进程之fork
进程之fork
父子进程的不同点
子进程和父进程相同点多于不同点,一般来说我们记住不同点会更加省事
这里只列出了部分不同点
- fork返回值不同
- 进程ID不同
- 父进程ID不同
- 子进程不继承父进程的文件锁
- 子进程未处理的闹钟被清除
- 子进程未处理信号集设置为空集
父子进程的拷贝现象
子进程与父进程环境几乎是一样的,它几乎拷贝了父进程的所有内容(为了减少消耗使用了写时复制技术),在这里,我得说明一下"拷贝"含义,"拷贝"是指父进程与子进程各自有一份资源副本,他们对各自资源副本的修改不会对对方产生任何影响.
子进程从父进程继承的属性
这里我说的是继承,但实际上等同于拷贝,他们对这些资源的修改不会对对方产生任何影响
这里只列出了一部分
- 实际UID,实际GID,有效UID,有效GID
- 进程组ID
- 会话ID
- 控制终端
- 当前工作目录
- umask屏蔽字
- 已打开的文件描述符及其文件表项(如对描述符执行了dup函数那样)
- 资源限制
- 根目录
- 标准输出缓冲区
直观感受拷贝现象
写代码时,我们可以通过下面的图片来直观地感受到拷贝所发生的位置
从内存布局中理解拷贝现象
上图中看到的是一个直观的现象,但没有深入到本质,接下来我们从内存布局的角度上进行更深入地了解
上图是Linux32位x86架构上运行的进程的标准内存布局
而子进程实际上拷贝的是父进程的
- 栈(Stack)
- 堆(Heap)
- BSS段(BSS Segment)
- 数据段(Data Segment)
- 代码段(Text Segment)
没有被拷贝的部分,或者说共享的部分是
- 内核虚拟空间(Kernel Space)
- 文件映射区(Memory Mapping Segment)
下面我们来分析一个经典的问题来加深对进程拷贝的理解
int main(void)
{
int i;
for(i=0;i<2;i++)
{
fork();
printf("X");
}
return 0;
}
//执行上述程序后,其输出结果是?
在分析上述程序时,我们需要注意两个发生拷贝的地方
i变量
标准输出缓冲区
标准输出默认是行缓冲的,所以父进程的缓冲区同样会被拷贝一份到子进程当中,只有遇到\n
或者进程终止时才把缓冲区内容flush到控制终端
理解了上面两点之后,我们随之画出它的状态图,如下图
图中右上角是状态图的含义, 可以看到,该段代码创建了四个进程, 当每个进程都执行完最后一句printf
后退出时,他们将清空标准输出缓冲区,控制终端上将会出现XXXXXXXX
,即八个X
父进程和子进程谁先执行?
fork之后,应假设两个进程同时运行,甚至能在汇编甚至机器代码层面上交替运行,其执行过程的分析方法与线程相同.这里我留到讲线程的专题中讲解.
进程同步
一般来说,进程是分离的个体,应尽量减少信息交互(因其交互过程复杂,更好地替代方法是使用线程),但如果我们确实要如此,进程之间也是可以共享信息的.
进程通信需要满足两个条件
- 一有通讯方法
- 管道
- 命名管道
- 消息队列
- 信号量
- 共享内存
- 网络Socket
- 二有同步方法
- 锁
这里我不打算涉及进程通讯方法,而只讨论进程同步方法,下面给出了同步进程锁的简单实现例子
简易进程调度器
#include <stdio.h>
#include <getopt.h>
#include <string.h>
#include <pthread.h>
#include <unistd.h>
#include <sys/mman.h>
#define TASK_N 3 //fork()创建的工作进程个数
//工作进程工作是否完成
enum status{
FINISHED,
NOTYET
};
//描述工作进程的结构体
struct task{
pid_t pid;
pthread_mutex_t m;
enum status s;
};
int main(int argc,char* argv[]) {
struct task* tasks[TASK_N];
//每个子进程都保留一个与父进程互斥的锁,父进程轮流选中工作进程,被选中的子进程可以展开工作,没被选中的子进程将保持待命状态
//这里需要注意两点:
//1. 必须把锁映射到文件映射区,否则将会导致lock()将会失效
//2. 锁的属性需要设置为进程共享的 PTHREAD_PROCESS_SHARED
for(int i=0;i<TASK_N;i++)
{
tasks[i] = (struct task*)mmap(NULL,TASK_N*sizeof(struct task),PROT_READ|PROT_WRITE,MAP_SHARED|MAP_ANON,-1,0);
}
pid_t pid;
//初始化锁的属性以及工作进程状态
pthread_mutexattr_t attr;
for(int i=0;i<TASK_N;i++)
{
pthread_mutexattr_init(&attr);
pthread_mutexattr_setpshared(&attr, PTHREAD_PROCESS_SHARED);
pthread_mutex_init(&tasks[i]->m,&attr);
pthread_mutex_lock(&tasks[i]->m);
tasks[i]->s = FINISHED;
}
pthread_mutexattr_destroy(&attr);
for(int i=0;i<TASK_N;i++)
{
if((pid =fork()) < 0){
perror("fork failed!");
return -1;
}else if(pid == 0){
pid_t cpid = getpid();
//记录子各子进程ID
tasks[i]->pid = cpid;
printf("child[%d] fork successfully!\n",cpid);
while(1)
{
sleep(2);
/*debug:print task list
for(int k=0;k<TASK_N;k++)
{
printf("tasks[%d].pid=%d\n",k,tasks[k]->pid);
}*/
int j;
//子进程找到自己的锁
for(j=0;j<TASK_N;j++)
{
if(tasks[j]->pid == cpid)break;
}
if(j>=TASK_N){
fprintf(stderr,"child cannot find himself!\n");
return -2;
}
//printf("child[%d] waiting for lock...\n",tasks[j]->pid);
pthread_mutex_lock(&tasks[j]->m);
printf("I'm child[%d]. I have lock!.I can do myjob...\n",tasks[j]->pid);
sleep(1);
printf("child[%d]: job done ,give lock to manager!\n",tasks[j]->pid);
tasks[j]->s = FINISHED;
pthread_mutex_unlock(&tasks[j]->m);
}//child will not go out for loop
}
}
sleep(1);
//轮流选中子进程,被选中子进程开始工作
while(1)
{
for(int i=0;i<TASK_N;i++)
{
printf("manager: child[%d] go!\n",tasks[i]->pid);
tasks[i]->s = NOTYET;
pthread_mutex_unlock(&tasks[i]->m);
while(tasks[i]->s != FINISHED);
pthread_mutex_lock(&tasks[i]->m);
}
}
return 0;
}
代码执行结果:
child[48490] fork successfully!
child[48492] fork successfully!
child[48491] fork successfully!
manager: child[48490] go!
I'm child[48490]. I have lock!.I can do myjob...
child[48490]: job done ,give lock to manager!
manager: child[48491] go!
I'm child[48491]. I have lock!.I can do myjob...
child[48491]: job done ,give lock to manager!
manager: child[48492] go!
I'm child[48492]. I have lock!.I can do myjob...
child[48492]: job done ,give lock to manager!
manager: child[48490] go!
I'm child[48490]. I have lock!.I can do myjob...
child[48490]: job done ,give lock to manager!
manager: child[48491] go!
I'm child[48491]. I have lock!.I can do myjob...
child[48491]: job done ,give lock to manager!
manager: child[48492] go!
I'm child[48492]. I have lock!.I can do myjob...
child[48492]: job done ,give lock to manager!
manager: child[48490] go!
I'm child[48490]. I have lock!.I can do myjob...
child[48490]: job done ,give lock to manager!
manager: child[48491] go!
I'm child[48491]. I have lock!.I can do myjob...
Process finished with exit code 15
已知存在问题:
- 子进程为了找到自己的锁需要遍历所有的子进程,这里做个哈希会好一些
- 子进程如果先退出则会出现僵尸进程
参考资料
UNIX环境高级编程(APUE)