跌跌撞撞,unix也看到了进程管理,顿时觉得高大上了。然而面对第一个系统调用fork,这英语发音实在是蹩脚,差点就读成~~。
在unix里面,对于任何一个进程,都有一个唯一表示的进程ID(pid)当然除了进程ID这个标识之外还有很多其他属性:父进程ID(ppid),进程实际用户ID,进程有效用户ID,进程实际用户组ID,进程有效用户组ID。对于这些属性,可以使用下列函数获取
#include<unistd.h> pid_t getpid(void); pid_t getppid(void); uid_t getuid(void); uid_t geteuid(void); gid_t getgid(void); gid_t getegid(void);
值得注意的是,这些函数都没有返回出错。
函数fork
#include<unistd.h> pid_t fork(void);
很奇特,它的返回值会有两个,对于子进程返回0,对于父进程,返回子进程ID,如果出错返回-1。
派生出来新的子进程会继承父进程的好多东西,什么实际用户ID啊,等等好多,如果你有一本unix编程的书籍,上面肯定会有这些,这里就不罗嗦了,这里关键探讨一下书里面一些比较难理解的地方。
#include<stdio.h> #include<unistd.h> //测试fork的用法 int main(int argc,char* argv[]) { printf("before fork~\n"); pid_t pid=fork(); if(pid == 0) //fork函数返回0说明是执行子进程 { printf("this is child\n"); } else if(pid > 0) //fork函数返回大于0 说明返回的是子进程的pid号 { printf("this parent\n"); } return 0; //两个进程的执行顺序是不一定的, //这取决于系统的调度 }
我们来看看上面的程序,虽然代码少,但是大有乾坤。首先是printf,对于到标准输出流的IO而言,是行缓冲。所以在行缓冲满了,或者碰到\n刷新缓冲区的时候,缓冲区的数据才会被冲洗。然而fork函数出来的子进程是会继承父进程的缓冲区的。所以如果缓冲区里的数据没被刷新,这意味着子进程也会从父进程那里得到一份"before fork~",那么程序在执行的时候会很神奇的输出两个before
fork~,当然,我们上面的代码只会输出一次,因为它用\n冲刷了缓冲区。
还有一点是,如果程序执行的时候被重定向到了文件里,那么情况就有点不大一样了,因为这个时候是全缓冲,即使碰到\n也不会刷新缓冲区,这样子肯定可以看到两个before fork~!
还有一个情况是,对于父进程用open打开一个文件之后,fork出来的子进程是和父进程拥有统一个文件表的,这意味着他们可以改变对方的文件偏移量,就好像是用dup函数复制而来的一样!
最后,对于两个进程,他们的执行顺序是不一样的,这决定于系统的调度,,所以你多次执行这个程序,你会发现,每次执行的结果都不尽相同。如果想控制进程执行的顺序,就得使用wait函数或者后面的信号了。那个到时候再说~
那么,我们知道,我们一般派生出一个新的进程 都是为了执行不同的任务,所以一般fork子进程之后都会马上调用exec函数(以后会碰到)执行新的代码,所以为了提高效率,往往会用一个vfork的函数,vfork函数和fork函数的不同之处在于:fork函数会复制一份父进程的数据空间。而vfork不会。(简单讲就是fork之后的子进程不会改变父进程的变量值,但是vfork会),理解这句话的办法就是举个栗子~
#include<stdio.h> #include<unistd.h> int main(int argc,char* argv[]) { printf("before fork~\n"); int test=1; pid_t pid=fork(); if(0==pid) { printf("this is child\n"); test++; } else if(pid>0) { printf("this is parent\n"); printf("test=%d\n",test); } }
我们可以看到,在子进程的代码片里,我们执行了test++语句,但是在父进程里输出的时候test还是等于1,这意味着子进程和父进程有各自的数据空间,两者的改变不会影响彼此,但是vfork就不会了,vfork的思想是我们信任程序fork之后就要使用exec执行其他程序了,所以没必要为子进程开辟一块空间来保存父进程的变量。所以vfork出来的子进程和父进程是共享统一块内存空间的。所以这个时候如果你在子进程里改变了父进程的变量值,会影响到父进程的值。
最后讲一下fork的情况,对于fork函数,系统也不会真的就开辟一块完整的空间来给子进程用以复制父进程的内容,而是才是写时复制(copy-on-write)的方法,就是父进程和子进程仍然共享数据段,栈,堆空间,只是系统会把这段空间的权限变为只读,如果父进程或者子进程试图修改某个空间,这个时候系统才会复制仅仅是那个空间的数据。以此来达到效率的提高。