zoukankan      html  css  js  c++  java
  • 进程之fork

    进程之fork

    父子进程的不同点

    子进程和父进程相同点多于不同点,一般来说我们记住不同点会更加省事

    这里只列出了部分不同点

    • fork返回值不同
    • 进程ID不同
    • 父进程ID不同
    • 子进程不继承父进程的文件锁
    • 子进程未处理的闹钟被清除
    • 子进程未处理信号集设置为空集

    父子进程的拷贝现象

    子进程与父进程环境几乎是一样的,它几乎拷贝了父进程的所有内容(为了减少消耗使用了写时复制技术),在这里,我得说明一下"拷贝"含义,"拷贝"是指父进程与子进程各自有一份资源副本,他们对各自资源副本的修改不会对对方产生任何影响.

    子进程从父进程继承的属性

    这里我说的是继承,但实际上等同于拷贝,他们对这些资源的修改不会对对方产生任何影响

    这里只列出了一部分

    • 实际UID,实际GID,有效UID,有效GID
    • 进程组ID
    • 会话ID
    • 控制终端
    • 当前工作目录
    • umask屏蔽字
    • 已打开的文件描述符及其文件表项(如对描述符执行了dup函数那样)
    • 资源限制
    • 根目录
    • 标准输出缓冲区

    直观感受拷贝现象

    写代码时,我们可以通过下面的图片来直观地感受到拷贝所发生的位置

    1583047449055

    从内存布局中理解拷贝现象

    上图中看到的是一个直观的现象,但没有深入到本质,接下来我们从内存布局的角度上进行更深入地了解

    1583046687259

    上图是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变量
    • 标准输出缓冲区 标准输出默认是行缓冲的,所以父进程的缓冲区同样会被拷贝一份到子进程当中,只有遇到 或者进程终止时才把缓冲区内容flush到控制终端

    理解了上面两点之后,我们随之画出它的状态图,如下图

    1583046311083

    图中右上角是状态图的含义, 可以看到,该段代码创建了四个进程, 当每个进程都执行完最后一句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!
    ",cpid);
                while(1)
                {
                    sleep(2);
    				
                    /*debug:print task list
                    for(int k=0;k<TASK_N;k++)
                    {
                        printf("tasks[%d].pid=%d
    ",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!
    ");
                        return -2;
                    }
    
                    //printf("child[%d] waiting for lock...
    ",tasks[j]->pid);
                    pthread_mutex_lock(&tasks[j]->m);
                    printf("I'm child[%d]. I have lock!.I can do myjob...
    ",tasks[j]->pid);
                    sleep(1);
                    printf("child[%d]: job done ,give lock to manager!
    ",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!
    ",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
    

    已知存在问题:

    • 子进程为了找到自己的锁需要遍历所有的子进程,这里做个哈希会好一些
    • 子进程如果先退出则会出现僵尸进程

    参考资料

    Linux内存布局

    Linux内核层面内存布局

    UNIX环境高级编程(APUE)

  • 相关阅读:
    百度echarts插件x轴坐标显示不全决解方法
    Laravel
    Mysql 递归获取多重数组数据
    Laravel 怎么使用资源控制器delete方法
    Laravel 怎么在 blade 视图中将带 HTML 字符原样输出
    laravel sql复杂语句,原生写法----连表分组
    mysql连表分组报错---- sql_mode=only_full_group_by问题解决
    Bootstrap -- 模态框实现拖拽移动
    xcode6模拟器UITextField不能自动弹出键盘
    在Linux系统中使用rpm包安装FTP服务
  • 原文地址:https://www.cnblogs.com/virgildevil/p/12397129.html
Copyright © 2011-2022 走看看