进程

进程环境

当内核执行C程序时(使用一个exec程序),在调用main之前调用一个特殊的启动例程。可执行文件将此可执行例程指定为程序的起始地址–这是由链接器设置的,而连接器是有C编译器调用的。启动例程从内核取得命令行参数和环境变量值,然后按上述方式调用main函数。

进程控制

系统中有一些专用进程,ID为0的进程通常是调度进程,该进程是内核的一部分,它并不执行任何磁盘上的程序。进程为1的进程通常是Init进程,该进程负责在自举内核后启动unix系统,并将系统引导到一个状态,MacOs中的init进程变成了 launchd进程。

fork

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
pid_t pid = fork();

if(pid <0)
{
//error
printf("fork err\n");
}
else if( pid == 0 )
{
// child
sleep(50);
printf("child process by fork\n");
}
else
{
printf("fork me\n");
}

vfork

vfork()跟fork()类似,都是创建一个子进程,这两个函数的的返回值也具有相同的含义。但是vfork()创建的子进程基本上只能做一件事,那就是立即调用_exit()函数或者exec函数族成员,调用任何其它函数(包括exit())、修改任何数据(除了保存vfork()返回值的那个变量)、执行任何其它语句(包括return)都是不应该的。

此外,调用vfork()之后,父进程会一直阻塞,直到子进程调用_exit()终止,或者调用exec函数族成员。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
pid_t pid = vfork();
if( pid == 0 )
{
// child process
printf("child process run and _exit() \n");
_exit(0);
}
else if( pid > 0 )
{
// father process
printf("father process run \n");
}
else
{
printf("vfork error");
}

clone

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
void thread(void)
{
int i;
while(1)
{
printf("This is a pthread.\n");
sleep(1);
}
}

int main(int argc,char **argv)
{
pthread_t id;
int i,ret;
ret=pthread_create(&id,NULL,(void *) thread,NULL);
if(ret!=0){
printf ("Create pthread error!\n");
exit (1);
}

while(1)
{
printf("This is the main process.\n");
sleep(1);
}
pthread_join(id,NULL);
return 0;
}

编译,strace跟踪,看pthread_create 的系统调用是 clone, 其比较大的作用是创建线程,当然也能创建子进程
man clone

1
2
3
4
5
6
clone(child_stack=0x7f61669d6fb0,
flags=CLONE_VM|CLONE_FS|CLONE_FILES|CLONE_SIGHAND|\
CLONE_THREAD|CLONE_SYSVSEM|CLONE_SETTLS|CLONE_PARENT_SETTID|CLONE_CHILD_CLEARTID,
parent_tidptr=0x7f61669d79d0,
tls=0x7f61669d7700,
child_tidptr=0x7f61669d79d0) = 22372

看上面这个pthread的例子,只有一个pid,但是下面两个线程,其对应两个内核上的 task_struct

1
2
3
4
[root@VM_8_16_centos ~]# ll /proc/26733/task/
total 0
dr-xr-xr-x 5 root root 0 Sep 17 12:06 26733
dr-xr-xr-x 5 root root 0 Sep 17 12:06 26734

3个fork

传统的fork()系统调用时在Linux中是用clone()实现的,其中clone()的flags参数指定为SIGCHLD信号及所有清0的clone标志,而它的child_stack参数是父进程当前的堆栈指针。因此,父进程和子进程暂时共享同一个用户堆栈。而由于写时复制技术,当任何一个进程试图改变栈,则立即各自得到用户堆栈的一份拷贝。

do_fork()函数负责处理clone()、fork()和vfork()系统调用,也就是说,无论是clone()、fork()还是vfork(),都会调用do_fork()函数。

1:
image

2.
image

waitpid

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
int main()
{
pid_t pid = fork();
printf("pid= %ld\n" , (long) getpid());
if( pid < 0 )
{
printf("fork error");
}
else if( pid == 0 )
{
// child
if( (pid = fork()) < 0 )
{
printf("fork error");
}
else if( pid > 0 )
{
printf("pid= %ld will be exit, before child\n" , (long) getpid());
exit(0);
}
sleep(2);// a time for wait father process exit
printf("second child ppid= %ld\n" , (long) getppid());
printf("pid= %ld will be exit as child\n" , (long) getpid());
exit(0);
}

if( waitpid(pid,NULL,0) != pid )
{
printf("waitpid error");
}
printf("pid= %ld will be exit\n" , (long) getpid());
return 0;
}

REF

-->