• C语言笔记-18-Linux基础-进程


    C语言笔记-18-Linux基础-进程



    前言

    自学笔记,没有历史知识铺垫(省略百度部分)C语言笔记-18-Linux基础-进程


    一、进程概括

    1. 程序是计算机指令集合,静态数据
    2. 进程是程序在计算机运行的实例
    3. 程序在运行过程中,要使用计算机资源,就要对进程使用的资源进行记录,所有的记录约等于进程
    4. 进程是资源分配的基本单位,每个进程都有自己的pid和PCB

    注意:

    1. 进程有独立的4G地址空间(多个进程轮流布局同一份4G地址空间,同一时刻只有一个进程独立占用)
    2. 进程有自己的pid
    3. 进程有自己的PCB

    二、进程指令

    1. ps 查看进程
    2. top 实时查看进程

    PCB成员
    fd 记录进程对文件资源的使用情况(文件描述符)
    image 进程镜像(轮流布局独立4G地址空间映射)

    三、进程函数

    unistd.h

    fork 新建子进程

    pid_t fork(void); 新建子进程(新建失败时,不会创建子进程)
    返回值: 成功时,在父进程和子进程中各返回一次(常说的一次调用,两次返回)

    1. 成功:pid 子进程的pid 返回到父进程中
    2. 成功:0 返回到子进程中
    3. 失败:-1 返回到父进程中,errno被设置

    注意:

    1. 子进程的PCB和父进程的PCB读的部分一致,写的部分,子进程会复制父进程PCB该部分后单独存储(2.6版本后增加,写时拷贝技术),即image布局是一样,但是子进程与父进程各运行在独立的内存空间,二者操作本进程时,不会相互影响

    可以通过fork返回标识,控制业务在父进程or子进程中处理(异步)

    示例代码

    #include 
    #include 
    #include 
    
    int main(int argc, char *argv[])
    {
        pid_t pid = fork();
        if (pid == -1)
        {
            printf("父进程执行失败,该信息永远在父进程中输出\n");
        }
        if (pid == 0)
        {
            printf("子进程pid才会返回0,该信息永远是在子进程中输出\n");
        }
        else
        {
            printf("父进程才会返回子进程的pid:%d,该信息永远在父进程中输出\n", pid);
        }
        printf("由于子进程复制父进程的PCB,该信息会在父进程和子进程都会输出\n");
        return 0;
    }
    
    // 输出结果
    父进程才会返回子进程的pid:7724,该信息永远在父进程中输出
    由于子进程复制父进程的PCB,该信息会在父进程和子进程都会输出
    子进程pid才会返回0,该信息永远是在子进程中输出
    由于子进程复制父进程的PCB,该信息会在父进程和子进程都会输出
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28

    return/exit 关闭子进程

    return 标识函数结束,在main函数中,终止进程
    exit 终止进程 (stdio操作会被刷新关闭,创建的临时文件也会被移除) 并向父进程返回(status&0377)(0-255)

    atexit 遗言函数

    进程在正常终止时被调用的函数,如:main函数return后、进程exit后,才会调用的函数

    注册遗言函数
    atexit() 传入无行参的指针函数

    1. 执行顺序按照注册顺序倒叙执行
    2. 同一个函数可以被注册多次,执行多次
    3. 子进程继承父进程的遗言函数
    4. exec家族中的函数,只要有一个函数调用成功,其他遗言函数会被移除(不执行)
    #include 
    #include 
    
    void endCall(){
        printf("遗言函数触发\n");
    }
    
    int main(int argc, char *argv[])
    {
        atexit(endCall);
        atexit(endCall);
        return 0;
    }
    
    // 输出结果
    遗言函数触发
    遗言函数触发
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    注册遗言函数
    on_exit() 传入有行参的指针函数,并传入返回值,功能与atexit()一样

    四、wait 进程资源回收

    进程终止的时候,需要将进程资源回收,终止的进程无法自己回收,回收操作由父进程进行。
    回收函数wait家族函数回收

    wait

    阻塞并挂起当前进程,等待任意子进程终止。
    wait(int *status)
    入参:
    *status 将子进程状态信息存储到该指针中
    返回值:
    成功: pid 返回终止了的子进程的pid
    失败: -1 errno被设置

    wait的宏

    1. WIFEXITED(status) 子进程正常终止,返回真
    2. WEXITSTATUS(status) 返回子进程的退出状态码(exit(status) status&0377)
      这个宏只能在WIFEXITED返回真时使用
    1. WIFSIGNALED(status) 如果子进程是被信号终止的,返回真
    2. WTERMSIG(status) 返回导致子进程终止的信号的编号
      这个宏只能在WIFSIGNALED返回真时使用

    wait可以让异步执行的多进程,转变为同步执行
    模拟子进程正常终止示例

    #include 
    #include 
    #include 
    #include 
    
    int main(int argc, char *argv[])
    {
        int subExitStatus;
        pid_t pid = fork();
        if (pid == -1)
        {
            printf("父进程执行失败,该信息永远在父进程中输出\n");
        }
        if (pid == 0)
        {
            printf("子进程pid才会返回0,该信息永远是在子进程中输出\n");
            return 99;
        }
        else
        {
            wait(&subExitStatus);
    
            if (WIFEXITED(subExitStatus))
            {
                printf("子进程正常终止状态码:%d\n", WEXITSTATUS(subExitStatus));
            }
    
            printf("父进程才会返回子进程的pid:%d,该信息永远在父进程中输出\n", pid);
        }
        printf("由于子进程被提前return,该信息永远在父进程中输出\n");
        return 0;
    }
    
    // 执行结果
    子进程pid才会返回0,该信息永远是在子进程中输出
    子进程正常终止状态码:99
    父进程才会返回子进程的pid:12483,该信息永远在父进程中输出
    由于子进程被提前return,该信息永远在父进程中输出
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38

    模拟子进程信号打断示例

    #include 
    #include 
    #include 
    #include 
    #include 
    
    int main(int argc, char *argv[])
    {
        int subExitStatus;
        pid_t pid = fork();
        if (pid == -1)
        {
            printf("父进程执行失败,该信息永远在父进程中输出\n");
        }
        if (pid == 0)
        {
            printf("子进程pid才会返回0,该信息永远是在子进程中输出,pid:%d\n", getpid());
            getchar();
            return 99;
        }
        else
        {
            wait(&subExitStatus);
    
            if (WIFEXITED(subExitStatus))
            {
                printf("子进程正常终止状态码:%d\n", WEXITSTATUS(subExitStatus));
            }
            if (WIFSIGNALED(subExitStatus))
            {
                printf("子进程信号打断状态码:%d\n", WTERMSIG(subExitStatus));
            }
    
            printf("父进程才会返回子进程的pid:%d,该信息永远在父进程中输出\n", pid);
        }
        printf("由于子进程提前被信号终打断,该信息永远在父进程中输出\n");
        return 0;
    }
    
    // 执行结果
    子进程pid才会返回0,该信息永远是在子进程中输出,pid:13385
    子进程信号打断状态码:8
    父进程才会返回子进程的pid:13385,该信息永远在父进程中输出
    由于子进程提前被信号终打断,该信息永远在父进程中输出
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44

    打断信号

     dony15$ kill -8 13385
    
    • 1

    waitpid

    阻塞并挂起当前进程,等待指定子进程终止。
    pid_t waitpid(pid_t pid, int *stat_loc, int options);
    pid 指定具体的子进程

    1. <-1 等待进程组的pid是|pid|的组中的子进程
    2. -1 等待任意子进程
    3. 0 等待和当前进程同一个进程组的子进程
    4. >0 指定了要等待的子进程的pid

    stat_loc 同wait(int *status)的参数
    options

    1. WNOHANG 如果没有子进程终止,立即返回
    2. 0 阻塞等待子进程的终止

    返回值:
    成功 返回状态改变的子进程的pid
    如果WNOHANG被指定,没有子进程状态的改变 返回0
    错误 -1 errno被设置

    // 效果
    wait(s)==waitpid(-1,s,0)

    五、exec家族函数 进程映像的更新

    子进程可以更新image,此时子进程的image与父进程的image将不同(更新子进程image不影响父进程)

    execve exec核心函数,所有exec家族函数都是围绕这个函数封装
    int execve(const char *path, char *const argv[], char *const envp[]);
    功能:执行程序
    参数:

    1. filename 要执行的文件名
      这个文件必须是可执行的二进制文件脚本文件
    2. argv 字符串数组 传递给main函数的参数 习惯上第一个是可执行文件的名字
    3. envp name=value 作为环境变量传递给新的可执行程序
      argv和envp以NULL作为结尾
      返回值:
      成功 不返回
      错误 -1 errno被设置

    exec家族函数列表

    #include 
    //指向当前进程的环境变量列表
    extern char **environ;  
    /* (char  *) NULL */);
    int execl(const char *path, const char *arg, ...
                           
    int execlp(const char *file, const char *arg, ...
                           /* (char  *) NULL */);
    int execle(const char *path, const char *arg, ...
                           /*, (char *) NULL, char * const envp[] */);
    int execv(const char *path, char *const argv[]);
    int execvp(const char *file, char *const argv[]);
    int execvpe(const char *file, char *const argv[],char *const envp[]);
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    exec 公有的
    l l代表list,需要将arg元素以可变参数都传入
    v v代表vector,传数组即可
    p PATH环境变量 需要知道要加载的可执行程序所在的路径.

    1. 有p字母,会到环境变量PATH指定的路径中找可执行程序
    2. 没有p字母,必须指定可执行文件的所在路径(相对 绝对)

    e

    1. 如果有字母e,用户指定环境变量传递给新的可执行程序.
    2. 没有字母e.代表从父进程继承环境变量

    execvp示例

    #include 
    #include 
    #include 
    
    int main(int argc, char *argv[])
    {
        pid_t pid = fork();
        if (pid == -1)
        {
            printf("父进程执行失败,该信息永远在父进程中输出\n");
        }
        if (pid == 0)
        {
            char *ps_argv[] = {"ps", "-o", "pid,ppid,comm", NULL};
            execvp("ps", ps_argv);
            printf("execvp执行失败才会执行\n");
        }
        else
        {
            printf("父进程才会返回子进程的pid:%d,该信息永远在父进程中输出\n", pid);
        }
        printf("由于子进程镜像被更新(execvp执行失败,子程序才会执行),与父进程的PCB不同,该信息会在父进程输出\n");
        return 0;
    }
    
    // 执行结果
    父进程才会返回子进程的pid:14966,该信息永远在父进程中输出
    由于子进程镜像被更新(execvp执行失败,子程序才会执行),与父进程的PCB不同,该信息会在父进程输出
      PID  PPID COMM
      999   998 -bash
     5836  5805 /bin/bash
    10801 10800 -bash
    10839 10801 vim
    13388  5805 /bin/bash
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34

    execlp示例

    ...
    execlp("ps", "ps", "-o", "pid,ppid,comm", NULL);
    ...
    // 执行结果
    父进程才会返回子进程的pid:14966,该信息永远在父进程中输出
    由于子进程镜像被更新(execvp执行失败,子程序才会执行),与父进程的PCB不同,该信息会在父进程输出
      PID  PPID COMM
      999   998 -bash
     5836  5805 /bin/bash
    10801 10800 -bash
    10839 10801 vim
    13388  5805 /bin/bash
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    查看当前进程环境变量示例

    #include 
    #include 
    #include 
    
    // 当前进程的环境变量列表
    extern char **environ;
    
    int main(int argc, char *argv[])
    {
        for (size_t i = 0; environ[i] != NULL; i++)
        {
            printf("环境变量:%s\n", environ[i]);
        }
        return 0;
    }
    
    // 执行结果
    环境变量:SHELL=/bin/bash
    环境变量:TMPDIR=/var/folders/3j/nltc60p50b9dl803pp8qkbqc0000gn/T/
    环境变量:OLDPWD=/Users/dony15/Desktop/c_code/c_learn_3
    环境变量:ORIGINAL_XDG_CURRENT_DESKTOP=undefined
    环境变量:MallocNanoZone=0
    环境变量:JAVA_11_HOME=/Library/Java/JavaVirtualMachines/jdk-11.0.4.jdk/Contents/Home
    环境变量:LC_ALL=en_US.UTF-8
    环境变量:NO_PROXY=127.0.0.1
    环境变量:COCOS_CONSOLE_ROOT=/Users/dony15/Desktop/cocos2d-x-4.0/tools/cocos2d-console/bin
    环境变量:USER=dony15
    环境变量:COCOS_TEMPLATES_ROOT=/Users/dony15/Desktop/cocos2d-x-4.0/templates
    环境变量:COMMAND_MODE=unix2003
    环境变量:JAVA_12_HOME=/Library/Java/JavaVirtualMachines/jdk-12.0.2.jdk/Contents/Home
    环境变量:SSH_AUTH_SOCK=/private/tmp/com.apple.launchd.cT3LzZpnc1/Listeners
    环境变量:__CF_USER_TEXT_ENCODING=0x1F5:0x19:0x34
    ...
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33

    execle示例
    main.c

    #include 
    #include 
    
    int main(int argc, char *argv[])
    {
        char *m_env[] = {"name=abc", "age=15",NULL};
        int a = execle("./t_child.out", "abc", NULL, m_env);
        perror("err:");
        return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    t_child.c

    #include 
    #include 
    #include 
    
    // 当前进程的环境变量列表
    extern char **environ;
    int main(int argc, char *argv[])
    {
        printf("start t_child process pid:%d\n", getpid());
        for (size_t i = 0; argv[i] != NULL; i++)
        {
            printf("传入变量:%s\n", argv[i]);
        }
        for (size_t i = 0; environ[i] != NULL; i++)
        {
            printf("环境变量:%s\n", environ[i]);
        }
        printf("t_child结束\n");
        return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    dony15$ gcc -o t_child.out t_child.c 
    dony15$ gcc -o main main.c
    dony15$ ./main 
    start t_child process pid:9380
    传入变量:abc
    环境变量:name=abc
    环境变量:age=15
    t_child结束
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    六、Linux ELF 可执行文件

    elf格式为可执行文件,python、bash、自定义写的c.out等都是

    readelf 查看elf信息指令

    readelf -a main.out

    总结

    注意:
    char *argv[] 中argv是常量,不可以argv++
    char **environ; 中environ是变量,可以environ++

    向量表:地址空间连续的数组
    列表:地址空间可能不连续的数组

    1. 僵尸进程:进程已经终止,进程资源没有回收
    2. 孤儿进程:父进程已终止,子进程没有回收,这种子进程需要过继给另一个进程。
      1.ubuntu 15.04版本之前,过继给init进程(孤儿院)
      2.ubuntu 15.04版本之后,过继给upstart进程(孤儿院)

    exec家族函数,无论是环境变量列表、数组,还是入参变量列表、数组,结尾必须要加NULL

    本章主要为C语言笔记-18-Linux基础-进程

  • 相关阅读:
    java集合图谱
    数据结构 C语言 2.1 线性表抽象数据类型 2.2 小议顺序表
    中科院提出“思维传播”,极大增强ChatGPT等模型复杂推理能力
    Linux驱动开发十六.input系统——3.系统自带的input驱动
    【CSS】5分钟带你彻底搞懂 W3C & IE 盒模型
    软件项目管理 6.2.功能点估算法
    [ vulhub漏洞复现篇 ] ActiveMQ反序列化漏洞复现(CVE-2015-5254)
    微机原理:汇编语言语句类型与格式
    笙默考试管理系统-MyExamTest----codemirror(30)
    RabbitMQ初步到精通-第六章-RabbitMQ之死信队列
  • 原文地址:https://blog.csdn.net/weixin_41832302/article/details/128165783