进程环境
当内核执行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:
2.
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