• <Linux>进程控制


    目录

    一、进程创建

    写实拷贝:

    二、进程终止

    三、进程等待


    一、进程创建

    描述一下,fork创建子进程,操作系统都做了什么?

    fork后父子进程是全部包括之前的代码都共享;

            

    fork创建子进程(内核数据结构(OS创建) + 进程代码和数据(一般从磁盘中来,也就是C/C++程序加载之后的结果));

    创建子进程,给子进程分配对应的内核结构,必须是子进程自己独有的,因为进程具有独立性,理论上,子进程也要有自己的代码和数据,可是一般而言,我们没有加载的过程,也就是说,子进程没有自己的代码和数据,所以子进程只能使用父进程的代码和数据那么:

    代码:都是不可修改的,只能读取,所以父子共享是没有问题的!

    数据:可能被修改的,所以必须分离!

    对于数据:

    创建子进程时,不需要将不会访问的,或者只会读取的数据都拷贝一份,但是为了保证独立性,还是需要一定数据的拷贝,这里只拷贝将来会被父进程或者子进程写入的数据;但是一般而言,即使是操作系统也无法提前知道那些空间可能会被写入的,而且即使是提前知道了,也不能保证立马就使用拷贝的空间。所以OS选择了写实拷贝技术,来进行父子进程数据的分离。 

    写实拷贝:父子进程共用代码,只有在进行数据写入时才重新内存分布;

    为什么选择写实拷贝?

    1.用的时候在给你分配空间(高效)

    2.操作系统无法在代码执行前预知那些空间会被拷贝

    1.我们的代码汇编之后,会有很多行的代码,而且每行代码都加载到内存之后,都有对应的地址;

    2.因为进程随时可能被中断(可能并没有执行完),下次回来,还必须保证从之前的位置继续运行,就要求CPU必须随时记录下当前进程执行的位置,所以CPU内有对应的寄存器数据,用来记录当前进程的执行位置!

    寄存器在CPU内,只有一份,寄存器内的数据,是可以有多份的 !(进程的上下文)

    fork创建子进程的时候,虽然父子进程会各自调度,各种会修改EIP(存储进程代码位置的寄存器),但是已经不重要了,因为子进程已经认为自己的ETI起始值是在fork之后的代码了!!所以子进程会从fork之后的代码开始执行;

    写实拷贝:

    父子进程代码共享,子进程页表拷贝父进程页表,在子进程或者父进程进行写入操作时,页表发生改变,重新分配内存;

    因为有写实拷贝技术的存在,所以,父子进程得以彻底分离,完成了进程独立性的技术保证;

    写实拷贝是一种延时申请技术,可以提高整机的内存使用率;

    二、进程终止

    1.进程终止时,操作系统做了什么?

    释放进程中申请的相关内核数据结构和对应数据和代码;(本质就是释放系统资源)

    2.进程终止的常见方式?

    • a.代码跑完,结果正确 
    • b.代码跑完,结果不正确
    • c.代码没有跑完,程序崩溃了(信号部分,涉及一点点)

    main函数返回值的含义是什么?return 0的含义是什么?为什么总是0?

    0:退出码;返回上一进程,用来判断进程执行结果的,可以忽略。

    命令:echo $? // 获取上一个进程的退出码

    程序崩溃了,退出码无意义。一般而言退出码对应的return语句,没有被执行!

    3.用代码,如果终止一个进程?

    • main函数内的return语句就是终止进程的,return 退出码;
    • exit(int status)在任何地方调用,都表示终止进程;(库函数
    • _exit(int status)终止进程;(系统接口直接终止进程,不进行清理函数和缓冲区的刷新)

    库函数 vs 系统接口

    逐渐底层:语言 -> 库函数 -> 系统接口 -> 操作系统

    printf - \n 数据是存放在“缓冲区”的,那么这个”缓冲区“在哪里,谁维护的?

    一定不在操作系统内部;如果是操作系统维护的,那么_exit(int statut) 就可以刷新缓冲区;"缓冲区"是C标准库维护的;

    三、进程等待

    • 子进程退出,父进程不管子进程,子进程就要处于僵尸状态  ---- 导致内存泄漏
    • 父进程创建子进程,是让子进程办事的,那么子进程把任务完成得怎么样?父进程需要关心吗?如果需要,如何得知?如果不需要,该怎么处理?
    • 父进程通过进程等待的方式,回收子进程资源,获取子进程退出信息

    僵尸状态的进程,即使是kill -9也无法杀死一个已经死去的进程;

    如何等待?等待是什么?

    wait:回收僵尸进程的问题

    参数:wait(int *status)

    让父进程来等待子进程退出,并回收

    1. pid_t ret = wait(NULL);
    2. if (ret > 0)
    3. {
    4. printf("等待子进程成功,ret: %d\n", ret);
    5. }

     

    waitpid;获取子进程退出结果的问题

    参数:waitpid(pid_t pid, int *status, int option)

    • pid = -1 所有子进程
    • pid > 0 指定的子进程
    • status 获取子进程退出结果(退出码)输出型参数(是按照bit位的方式进行划分的,只使用低16位,高8位表示退出码)
    • option 默认为0 (阻塞态等待)(WNOHANG非阻塞态等待)

    进程异常退出,或者崩溃,本质是操作系统将这进程杀掉了 (通过发送信号的方式);

    status的低7位bit位,表示进程收到的信号!

    程序异常,不仅仅是内部代码的问题,也可能是外部杀掉了你的进程;

    • 父进程通过wait/waitpid可以拿到子进程的退出结果,为什么要用wait/waitpid呢?不直接使用全局变量?(不可以,进程具有独立性,写实拷贝)
    • 既然进程具有独立性的,进程的退出码不也是进程的数据码?父进程又怎么拿到的呢?wait/waitpid究竟做了什么呢?

    僵尸进程:至少要保留进程的PCB信息,task_struct里面保留了任何进程退出时的结果信息!可知wait/waitpid本质是读取了子进程的PCB结构体中的信息;

    • wait/waitpid有这个权限吗?

     当然有,它是系统调用,操作系统操作! 

    四、进程替换

    • 1、是什么?

    fork()后,父子进程各自执行父进程代码的一部分,父子代码共享,数据写时拷贝!

    如果子进程不和父进程共享代码,而是想自己执行一个全新的程序,程序替换是通过特定的接口,加载磁盘上的一个权限的程序(代码和数据),加载到调用进程的地址空间中

    原理:

    • 2、怎么办?

    1、不创建子进程

    1. 1 #include <iostream>
    2. 2 #include <vector>
    3. 3 #include <unistd.h>
    4. 4 #include <stdio.h>
    5. 5
    6. 6 int main()
    7. 7 {
    8. 8 printf("当前进程开始的代码\n");
    9. 9
    10. 10
    11. 11 printf("当前进程结束的代码\n");
    12. 12 return 0;
    13. 13 }
    14. ~

    execl进程替换函数; 

    1. 1 #include <iostream>
    2. 2 #include <vector>
    3. 3 #include <unistd.h>
    4. 4 #include <stdio.h>
    5. 5
    6. 6 int main()
    7. 7 {
    8. 8 printf("当前进程开始的代码\n");
    9. 9
    10. 10 execl("/usr/bin/ls", "ls", "-l", NULL);
    11. 11
    12. 12 printf("当前进程结束的代码\n");
    13. 13 return 0;
    14. 14 }

      运行结果

    execl是进程替换的函数,调用之后,进程所有的代码都会被替换,之后的代码就不会执行了!!! 

     execl为什么调用成功,没有返回值呢?

    调用成功后,将之前进程的返回值等都全部替换了;

    2、创建子进程

      简易的xshell代码

    1. 1 #include <stdio.h>
    2. 2 #include <unistd.h>
    3. 3 #include <sys/wait.h>
    4. 4 #include <stdlib.h>
    5. 5
    6. 6 int main()
    7. 7 {
    8. 8 pid_t id = fork();
    9. 9 if (id == 0)
    10. 10 {
    11. 11 //子进程
    12. 12 printf("子进程开始运行: pid:%d\n", getpid());
    13. 13 sleep(2);
    14. 14 execl("/usr/bin/ls", "ls", "-l", NULL);
    15. 15 exit(1);
    16. 16 }
    17. 17 else
    18. 18 {
    19. 19 //父进程
    20. 20 int status = 0;
    21. 21 printf("父进程开始运行: pid:%d\n", getpid());
    22. 22 pid_t id = waitpid(-1, &status, 0);
    23. 23
    24. 24 if (id > 0)
    25. 25 {
    26. 26 printf("wait success, exit code: %d\n", WEXITSTATUS(status));
    27. 27 }
    28. 28 }
    29. 29
    30. 30

    int execv(const  char * path, char *const argv[]); -- 用数组的方式传参数

    int execlp(cosnt char* file, char const* arg, ... ); --- 会自己在环境变量中查找

    1. int execlp("ls", "ls", "-a", "-l", "NULL");
    2. 前面的"ls"表示要执行谁;
    3. 后面的"ls"表示要如何执行;

    这些函数本质就是将自己的这些参数,传给可执行程序中的main函数;

    int execvp(const char* file, char *const  argv[]); --- 会自己在环境变量中查找

    1. char* const argv[] = { (char*)"ls",(char*)"-l" ,"NULL"};
    2. int execvp("ls", argv)

    int execle(const char* path, const char *arg, ... ,const char* envp[]); 

    1.如何执行其他我写的C、C++二进制可执行程序

    2.如何执行其他语言的程序

    1. //子进程
    2. 12 printf("子进程开始运行: pid:%d\n", getpid());
    3. 15 execlp("python", "python", "test.py", NULL);
    4. 16 exit(1)

    即:exec*就是加载器的底层接口!

    环境变量具有全局性

    为什么环境变量会有全局性呢?

    int execle(const char* path, const char *arg, ... ,const char* envp[]); 

    这个函数里面的const char* envp[],就是将父进程的环境变量传给子进程,如此递归下去,环境变量就具有了全局性;

    int execve (const char *filename, char *const argv [], char *const envp[]);

    真正的系统调用!!之前的都是系统提供的基本封装;

  • 相关阅读:
    ESP32控制器使用SX1278 LoRa模块的方法
    Intel 7工艺的极限!酷睿i9-14900K/i7-14700K首发评测:6GHz单核性能无敌
    实战:springboot整合rabbitMQ
    sql -- 聚合函数和GroupBy和Having的爱恨情仇
    如何实现让一个函数能返回多个值的效果
    RabbitMQ实践——最大长度队列
    Flutter dart语言特点总结
    Toronto Research Chemicals助力植物育种丨螺罗西芬
    【JavaScript】JavaScript 变量 ① ( JavaScript 变量概念 | 变量声明 | 变量类型 | 变量初始化 | ES6 简介 )
    linux C/C++ socket编程
  • 原文地址:https://blog.csdn.net/weixin_63246064/article/details/126887782