• APUE学习之进程控制


    最后编辑: 2019-11-6

    版本: gcc version 5.4.0 20160609 (Ubuntu 5.4.0-6ubuntu1~16.04.11)

    一、进程标识

    每一个进程都有一个唯一的非负整数的ID, 该类型为 pid_t. 当进程退出或者被杀死后,进程 ID 会被系统复用. 与文件句柄不同的是,大多操作系统使用的是延迟复用算法,这为了避免新的进程被误认为是某个已经被中止的先前进程.(顺次向下使用).

    系统中存在一些专有进程,例如 ID = 0 的调度进程(交换进程 swapper), ID = 1 的 init 进程. init 进程是所有进程的祖先进程.

    1.1 相关函数

    pid_t getpid(void); // 获取当前进程的 ID 
    
    pid_t getppid(void); // 获取父进程的 ID
    

    二、fork 函数

    fork 函数可用于创建一个新的进程, 新的进程称为当前进程的子进程, 相对的,当前进程也就是新进程的父进程.

    pid_t fork(void);
    
    1. fork 调用一次返回两次. 两次返回的区别在于, 子进程返回 0, 父进程返回新建子进程的进程 ID

      • 父进程返回新建子进程 ID 原因: 一个进程可以有多个子进程,但是系统没有一个函数能获取所有子进程 ID;
      • 子进程返回值为 0 的原因: 子进程可以通过 getppid 获取父进程 ID, (ID为0的就交换进程永远只会内核交换时候使用,子进程的父进程永远不能为0);
      • 返回值小于 0 表示异常
    2. 子进程与父进程继续执行 fork 之后的指令.子进程是父进程的副本. 但目前很多实现上采用写时复制技术(Copy-On-Write), fork 之后, 父子进程区域共享,但该区域变更为只读状态,当父进程或者子进程想要对该区域进行修改的时候, 内核将为父进程或子进程复制一份该区域作为副本使用.(谁修改,谁复制).

    3. fork 之后父子进程的调度先后顺序是不确定的,这取决于内核的调度算法;

    2.1 父子进程之间的区别:

    前面说到, 子进程是父进程的副本. 但是还是有如下的区别:

    1. fork 返回值不同;
    2. 进程的 ID 不同;
    3. 父进程的 ID 也不同;
    4. 子进程的 tms_utime/ tms_stime / tms_cutime / tms_ustime 的值设置为 0;
    5. 子进程不继承父进程设置的文件锁;
    6. 子进程的未处理闹钟被清除;(未决信号清零)
    7. 子进程的未处理信号集设置为空集;

    2.2 fork 使用场景

    1. 父进程希望复制自己, 例如网络服务中,父进程等待请求,接收到请求后 fork 自己,让子进程去处理请求, 父进程继续等待下一个请求;
    2. 一个进程要执行不同的程序. 例如 shell 终端;

    2.3 代码示例

    #include <stdio.h>
    #include <stdlib.h>
    #include <unistd.h>
    
    int globvar = 6;
    char buf[] = "a write to stdout 
    ";
    
    int main(int argc, char **argv)
    {
        int var;
        pid_t pid;
    
        var = 88;
    
        if (write(STDOUT_FILENO, buf, sizeof(buf) - 1) != sizeof(buf) - 1)
        {
            perror("write error");
        }
    
        printf("before fork 
    "); // not flush stdout
    
        if ((pid = fork()) < 0)
        {   
            perror("fork error ");
        } 
        else if (pid == 0) // child
        {
            globvar++;
            var++;
        }
        else // parent
        {
            sleep(2); // 等待子进程先执行
        }
    
        printf("pid = %d, glob = %d, var = %d 
    ", getpid(), globvar, var);
        exit(0);
    }
    
    

    执行结果:

    a write to stdout 
    before fork 
    pid = 6053, glob = 7, var = 89 
    pid = 6052, glob = 6, var = 88 
    

    fork 打印结果重定向到文件内部, 多次打印文件内容, fork 之前需要调用 fflush() 刷新流

    1. 终端是标准的输出设备,标准输出设备是行缓冲模式,当有 的时候, 会刷新缓冲区
    2. 文件是全缓冲模式, 全缓冲模式下 仅仅是一个换行的作用

    三、vfork 函数

    此函数有瑕疵, 仅作了解

    vfork 与 fork 调用的序列和返回值相同,但是两者语义上不同的.

    vfork 函数创建的新进程,该进程的目的是 exec 一个新函数. vfork 的子进程不会将父进程地址空间完全复制过去, 因为该进程目的是调用 exec. 但是, 在子进程 exec 或者 exit 之前, 子进程是在父进程的空间内运行, 这种在一定程度上提高效率.

    但如果子进程修改了父进程的数据,或者调用其他的函数,又或者没有 exec / exit返回, 都会产生未知错误. vfork 保证子进程优先被返回,但是如果子进程依赖于父进程的某个动作,这样就造成死锁.

    说了这么多,就是说一句, 不要用 vfork

  • 相关阅读:
    损失函数
    DPM 目标检测1
    编程题
    枚举型和元类
    python 多继承
    网络基础Cisco路由交换一
    网络基础tcp/ip协议五
    网络基础tcp/ip协议四
    网络基础tcp/ip协议三
    网络基础tcp/ip协议二
  • 原文地址:https://www.cnblogs.com/gaox97329498/p/11900683.html
Copyright © 2020-2023  润新知