linux进程之fork函数
来源:互联网 发布:阿波罗登月 知乎 编辑:程序博客网 时间:2024/05/22 17:02
fork函数:
fork函数的作用是从调用进程中创建一个新的进程,新的进程相当于是调用进程的副本,称为子进程,而调用进程称为父进程。
本节主要讲解父子进程之间的联系和区别。
函数原型:
#include <unistd.h>pid_t fork(void);
返回值:
- 在父进程中,fork返回新创建子进程的进程ID。
- 在子进程中,fork返回0。
- 如果出现错误,fork返回一个负值。
fork调用为什么会返回两次呢?
fork调用的一个奇妙之处就是它仅仅被调用一次,却能够返回两次,这也是fork难以理解的点。
由于子进程复制了父进程的堆栈段,所以两个进程都停留在fork函数中,等待返回。因此fork函数会返回两次,一次是在父进程中返回,另一次是在子进程中返回,这两次的返回值是不一样的。
fork函数的作用:
fork()函数通过系统调用创建一个与原来进程几乎完全相同的进程。子进程是父进程的副本,子进程获得父进程数据空间、栈和堆的副本,这是子进程所拥有的副本,但是父子进程并不共享这些存储空间部分。
接下来看看示例例子,看看实验结果是不是如上所说的。
示例程序:
/* fork.c*/#include <unistd.h> #include <stdio.h> #include<stdlib.h>int main () { pid_t pid; int count=0; if ((pid = fork()) < 0) { perror("fork"); exit(-1); } else if (pid == 0) { printf("child\n"); count++; } else { printf("parent\n"); sleep(1); } printf("pid = %ld,ppid = %ld, count = %d\n",(long)getpid(),(long)getppid(),count); return 0; }
以上程序fork之后,父进程相当于执行如下代码:
#include <unistd.h> #include <stdio.h> #include<stdlib.h>int main () { pid_t pid; int count=0; printf("parent\n"); sleep(1); printf("pid = %ld,ppid = %ld, count = %d\n",(long)getpid(),(long)getppid(),count); return 0; }
子进程相当于执行如下代码:
#include <unistd.h> #include <stdio.h> #include<stdlib.h>int main () { pid_t pid; //pid表示fork函数返回的值 int count=0; printf("child\n"); count++; printf("pid = %ld,ppid = %ld, count = %d\n",(long)getpid(),(long)getppid(),count); return 0; }
实验结果:
ubuntu:~/test/process_test$ gcc fork.c -o fork ubuntu:~/test/process_test$ ./forkparentchildpid = 88270,ppid = 88269, count = 1//隔一秒才打印下面父进程的logpid = 88269,ppid = 25872, count = 0
以上例子可以看出,子进程和父进程的count变量是不共享的,是各自存储空间的,从打印出来的count变量的值不相同可以证明这一点。
下面再来看一个例子,可以初步证明子进程是父进程的副本。
/* fork2.c*/#include <unistd.h> #include <stdio.h> #include<stdlib.h>int main () { pid_t pid; //pid表示fork函数返回的值 int count=0; if ((pid = fork()) < 0) { perror("fork"); exit(-1); } else if (pid == 0) { printf("child\n"); count++; printf("child: pid = %ld,ppid = %ld, count = %d\n",(long)getpid(),(long)getppid(),count); } else { printf("parent\n"); sleep(1); printf("parent:pid = %ld,ppid = %ld, count = %d\n",(long)getpid(),(long)getppid(),count); } return 0; }
实验结果:
ubuntu:~/test/process_test$ gcc fork2.c -o fork2ubuntu:~/test/process_test$ ./fork2parentchildchild: pid = 88754,ppid = 88753, count = 1//隔一秒才打印下面父进程的logparent:pid = 88753,ppid = 25872, count = 0
从实验结果可以看出,fork2.c和fork.c效果是一致的,证明了fork.c中父进程printf代码会被子进程拷贝一个副本。一般来说,在fork之后是父进程先执行还是子进程先执行是不确定的,这取决于内核所使用的调度算法。所以,这里我们让父进程sleep再打印log,是为了让子进程先执行打印log,方便我们对结果的分析。
还有一个重要的点是,子进程和父进程共享同一个文件偏移量。
下面用实例程序来测试文件偏移量是否共享。
/* open_fork.c*/#include <unistd.h> #include <stdio.h> #include<stdlib.h>#include <sys/types.h>#include <sys/stat.h>#include <fcntl.h>#define FILENAME "file_fork"/**测试文件偏移量是否共享*/int main () { pid_t pid; //pid表示fork函数返回的值 int count=0; char write_child_buf[] = "11111"; char write_parent_buf[] = "22222"; int fd = open(FILENAME,O_RDWR | O_CREAT,0777); if(fd < 0) { perror("open"); } if ((pid = fork()) < 0) { perror("fork"); exit(-1); } else if (pid == 0) { printf("child\n"); if (write(fd,write_child_buf,sizeof(write_child_buf))) { perror("write"); } count++; printf("child: pid = %ld,ppid = %ld, count = %d\n",(long)getpid(),(long)getppid(),count); } else { printf("parent\n"); sleep(2); if (write(fd,write_parent_buf,sizeof(write_parent_buf))) { perror("write"); } printf("parent:pid = %ld,ppid = %ld, count = %d\n",(long)getpid(),(long)getppid(),count); } return 0; }
实验结果:
ubuntu:~/test/process_test$ ./open_forkparentchildwrite: Successchild: pid = 6737,ppid = 6736, count = 1write: Successparent:pid = 6736,ppid = 25872, count = 0ubuntu:~/test/process_test$ cat file_fork 1111122222chenting
从上面的结果中,我们看到,子进程向文件file_fork写入字符串“11111”,然后父进程向file_fork向文件写入字符串“22222”,可以看到字符串“22222”是追加到字符串“11111”后面的,说明父子进程文件描述符、文件偏移量是有被共享的。
(以下内容参考《unix环境高级编程》)
这种共享文件的方式使父子进程对同一文件使用了一个文件偏移量,如果父子进程都向标准输出进行写操作,如果父进程的标准输出已经重定向,那么子进程写到标准输出时,它将更新与父进程共享的该文件的偏移量。比如当父进程等待子进程时,子进程写到标准输出,而在子进程终止后,父进程也写到标准输出,并且其输出会添加在子进程所写数据之后,如果父子进程不共享同一文件偏移量,这种形式的交互很难实现。除了打开文件之外,父进程的很多其他属性也由子进程继承,包括:
- 实际用户ID,实际组ID,有效用户ID,有效组ID。
- 附加组ID。
- 进程组ID。
- session ID。
- 控制终端。
- 设置用户ID标志和设置组ID标志。
- 当前工作目录。
- 根目录。
- 文件模式创建屏蔽字。
- 信号屏蔽和安排。
- 针对任意开打文件描述符的在执行时关闭(close-on-exec)标志。
环境。 - 连接的共享存储段。
- 资源映射。
- 资源限制。
父子进程的区别是:
- fork返回值。
- 进程ID不同。
- 两个进程具有不同的父进程ID。
- 子进程的tms_utime,tms_stime,tms_cutime以及tms_ustime均设置为0.
- 父进程设置的文件锁不会被子进程继承。
- 子进程的未处理alarm被清除。
- 子进程的未处理信号设置为空集。
- linux进程之fork函数
- Linux多进程之fork()函数
- Linux -- 进程管理之fork() 函数
- Linux进程创建之fork()函数
- 【linux进程】fork函数浅析
- Linux进程创建fork()函数
- 进程控制之fork函数
- 进程管理之fork函数
- Linux多进程之fork()和vfork()函数的对比
- Linux之fork函数
- Linux之fork函数
- Linux 进程之fork()讲解
- linux进程控制之用fork()函数打印n层进程二叉树
- linux系统编程之进程(四):wait/waitpid函数与僵尸进程、fork 2 times
- Linux系统编程(8)—— 进程之进程控制函数fork
- linux系统编程之进程(四):wait/waitpid函数与僵尸进程、fork 2 times
- Linux之fork()函数
- linux之fork函数浅析
- Struts2动态方法报错
- Mysql 报错Packets larger than max_allowed_packet are not allowed
- KMP相关算法
- HBase判断表是否存在
- map函数
- linux进程之fork函数
- SPFA
- javascript:除去数组的重复项
- Python实现:利用GBDT产生新特征(GBDT+Linear Regression)
- Java并发编程:Thread类的使用
- 向花生壳上传自己的网站
- 关于SpringMVC小结
- Java进阶之路——从初级程序员到架构师,从小工到专家。
- SQ项目-MM功能增强业务说明书