• Linux: 进程(控制)


    目录

    1.进程的创建

    1.1fork函数

    1.2fork创建子进程,OS做了什么?

    1.3为什么要写实拷贝?

    2.进程的终止

    2.1进程终止,操作系统做了什么?

    2.2进程常见的退出方式

    2.3进程常见的退出方法

    3.进程的等待

    3.1为什么进行进程等待

    3.2如何等待?

    wait方法

    waitpid方法

    3.3测试代码

    4.进程的替换

    4.1概念以及原理

    4.2 怎么做?

    1.替换函数exec

    2.测试代码

    4.3为什么要有程序替换

    1.场景需要

    2.补充:为什么要创建子进程


    学习目标:1.进程的创建 2.进程的终止 3.进程的等待 4.进程的替换

    1.进程的创建

    1.1fork函数

    1.功能:在已有的进程下创建一个新的进程。新进程:子进程 , 原进程:父进程

    2.引用的头文件:#include

    3.函数:pid_t fork(void);     使用:pid_t id = fork();

    4.返回值:创建成功:a.把子进程的id返回给父进程,b.把0返回给子进程

                  创建失败:返回-1

    5.特点:

    • 父子进程代码共享,数据各自开辟空间,私有一份(采用写时拷贝)

    1.2fork创建子进程,OS做了什么?

    • 分配新的内存块和数据结构给子进程
    • 将父进程部分数据结构内容拷贝给子进程(写时拷贝)
    • 添加子进程到系统进程列表中
    • fork返回,开始调度器调度

    1.3为什么要写实拷贝?

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

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

    1.用的时候,再给你分配,是高效使用内存的一种表现

    2.OS 无法再代码执行前, 预知哪些空间会被访问

    2.进程的终止

    2.1进程终止,操作系统做了什么?

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

    2.2进程常见的退出方式

    a. 代码跑完, 结果正确

    b. 代码跑完, 结果不正确 ---->main函数的返回值?有什么意义?

    c. 代码没有跑完, 程序崩溃了

    2.3进程常见的退出方法

    --正常终止(可以通过 echo $? 查看进程退出码):1.return(main函数内)  2.exit函数

    --非正常终止:ctrl  + c  ,信号终止

    exit函数:头文件:#include     函数: void exit(int status);

    _exit函数:头文件:#include     函数: void _exit(int status);

    参数:status 定义了进程的终止状态,父进程通过wait来获取该值

    exit会执行用户定义的清理函数, 然后刷新缓冲, 关闭流等, 然后再退出

    _exit直接退出

    3.进程的等待

    3.1为什么进行进程等待

    1.处理僵尸进程,解决内存泄漏的问题

    2.父进程可以通过等待知道派给子进程的任务完成的状况:正确与否,是否异常

    3.父进程通过等待,回收子进程资源,获取子进程退出信息

    3.2如何等待?

    wait方法

    1.头文件

    #include      #include
    函数:pid_t wait(int*status);


    2.返回值:成功返回被等待进程pid,失败返回-1。
    3.参数:输出型参数,获取子进程退出状态,不关心则可以设置成为NULL

    waitpid方法

    pid_ t waitpid(pid_t pid, int *status, int options);
    1.返回值:
            当正常返回的时候waitpid返回收集到的子进程的进程ID;
            如果设置了选项WNOHANG,而调用中waitpid发现没有已退出的子进程可收集,则返回0;
            如果调用中出错,则返回-1,这时errno会被设置成相应的值以指示错误所在;
    2.参数:

    --.pid:

               Pid=-1,等待任一个子进程。与wait等效。
               Pid>0.等待其进程ID与pid相等的子进程。


    --.status:

            
           获取状态码(即次第8位):(status>>8)&0xFF

           获取信号(即低7位):status & 0x7F

            

            WIFEXITED(status): 若为正常终止子进程返回的状态,则为真。(查看进程是否是正常退出)
            WEXITSTATUS(status): 若WIFEXITED非零,提取子进程退出码。(查看进程的退出码)


    --.options:

            默认为0:阻塞等待
            WNOHANG(非阻塞等待):若pid指定的子进程没有结束,则waitpid()函数返回0,不予以等待。若正常结束,则返回该子进程的ID。

    3.3测试代码

    1.阻塞式等待

    阻塞式等待,即把waitpid中的参数中options设为0

    代码:

    1. 1 #include <stdio.h>
    2. 2 #include<sys/types.h>
    3. 3 #include<sys/wait.h>//wait的头文件
    4. 4 #include<unistd.h> //fork的头文件
    5. 5 #include<stdlib.h>
    6. 6
    7. 7 int main()
    8. 8 {
    9. 9 pid_t id = fork(); //创建子进程
    10. 10 if(id == 0)
    11. 11 {
    12. 12 //子进程
    13. 13 int cnt = 5;
    14. 14 while(cnt)
    15. 15 {
    16. 16 printf("我是子进程: %d\n",cnt--);
    17. 17 sleep(1);
    18. 18 }
    19. 19 exit(11);
    20. 20 }
    21. 21 else
    22. 22 {
    23. 23 //父进程
    24. 24 int status = 0;
    25. 25 pid_t ret = waitpid(id,&status,0);//默认是阻塞式等待子进程
    26. 26 if(ret >0)
    27. 27 {
    28. 28 if(WIFEXITED(status))
    29. 29 printf("父进程等待成功,退出码: %d\n",WEXITSTATUS(status));
    30. 30 else
    31. 31 printf("子进程异常退出: %d\n",WIFEXITED(status));
    32. 32 }
    33. 33 }
    34. 34 return 0;
    35. 35 }

    效果:

    2.非阻塞式等待

    非阻塞式等待:即把waitpid中的参数中options设为WNOHANG

    我们想要父进程在等待的过程中干点别的事情:

    1.这里我们定义了一个类型为函数指针的vector

    2.定义了两个函数

    3.定义了一个load函数,用来往实例化的vector中填充函数指针

    4.当我们waitpid返回0,表示等待成功,但子进程还没有退出,若vector不为空,往里加载函数指针,让父进程调用这些函数(用来模拟做其它的事情)

    注意:这里使用了范围for去遍历vector,是C++11里面的,若是C98编译可能会出错

    使用下面这段代码:g++ -std=c++11 -0   myproc    myproc.cc

                                                           生成的执行文件           编译的文件

    代码:

    1. 1 #include<iostream>
    2. 2 #include<vector>
    3. 3 #include<stdio.h>
    4. 4 #include<sys/types.h>
    5. 5 #include<sys/wait.h>//wait的头文件
    6. 6 #include<unistd.h> //fork的头文件
    7. 7 #include<stdlib.h>
    8. 8
    9. 9 typedef void (*handler_t)();//函数指针类型
    10. 10 std :: vector<handler_t> handlers;//函数指针数组
    11. 11
    12. 12 void func_one()
    13. 13 {
    14. 14 printf("这是第一个临时任务\n");
    15. 15 }
    16. 16
    17. 17 void func_two()
    18. 18 {
    19. 19 printf("这是第二个临时任务\n");
    20. 20 }
    21. 21
    22. 22 void load()
    23. 23 {
    24. 24 handlers.push_back(func_one);
    25. 25 handlers.push_back(func_two);
    26. 26 }
    27. 27
    28. 28 int main()
    29. 29 {
    30. 30 pid_t id = fork(); //创建子进程
    31. 31 if(id == 0)
    32. 32 {
    33. 33 //子进程
    34. 34 int cnt = 5;
    35. 35 while(cnt)
    36. 36 {
    37. 37 printf("我是子进程: %d\n",cnt--);
    38. 38 sleep(1);
    39. 39 }
    40. 40 exit(11);
    41. 41 }
    42. 42 else
    43. 43 {
    44. 44 int quit = 0;
    45. 45 while(!quit)
    46. 46 {
    47. 47 int status = 0;
    48. 48 pid_t ret = waitpid(id,&status,WNOHANG);
    49. 49
    50. 50 if(ret > 0)
    51. 51 {
    52. 52 quit = 1;
    53. 53 printf("等待成功,退出码: %d\n",WEXITSTATUS(status));
    54. 54 }
    55. 55 else if(ret == 0)
    56. 56 {
    57. 57 printf("等待成功,但子进程还没有退出,父进程可以做其它的事情\n");
    58. 58 if(handlers.empty()) load();
    59. 59 for(auto& iter:handlers)
    60. 60 {
    61. 61 iter();
    62. 62 }
    63. 63 }
    64. 64 else
    65. 65 {
    66. 66 //等待失败
    67. 67 printf("等待失败\n");
    68. 68 quit = 1;
    69. 69 }
    70. 70 sleep(1);
    71. 71 }
    72. 72 }
    73. 73
    74. 74 return 0;
    75. 75 }

    效果:

    4.进程的替换

    4.1概念以及原理

    1.概念

    程序替换:是通过特定的接口,加载磁盘上一个全新的程序(代码+数据)

    2.原理

    4.2 怎么做?

    1.替换函数exec

    #include `//头文件

    1.函数
    int execl(const char *path, const char *arg, ...);
    int execlp(const char *file, const char *arg, ...);
    int execle(const char *path, const char *arg, ...,char *const envp[]);
    int execv(const char *path, char *const argv[]);
    int execvp(const char *file, char *const argv[]);

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

    2.命名解释

    l(list) : 表示参数采用列表
    v(vector) : 参数用数组
    p(path) : 有p自动搜索环境变量PATH
    e(env) : 表示自己维护环境变量

    3.返回值

    这些函数如果调用成功则加载新的程序从启动代码开始执行,不再返回。
    如果调用出错则返回-1
    所以exec函数只有出错的返回值而没有成功的返回值

    (这是因为exec函数是进程的替换,调用该函数成功之后,会将当前进程的所有代码和数据都进行替换! 包括已执行的和没有执行的!)

    加载 , 所谓的exec*函数, 本质就是如何加载程序的函数

    2.测试代码

    1.int execl(const char *path, const char *arg, ...);

    代码:

    1. 1 #include<stdio.h>
    2. 2 #include<unistd.h>
    3. 3 #include<sys/wait.h>
    4. 4 #include<sys/types.h>
    5. 5 #include<stdlib.h>
    6. 6
    7. 7
    8. 8 const char* path = "/usr/bin/ls";
    9. 9
    10. 10 int main()
    11. 11 {
    12. 12 pid_t id = fork();//创建子进程
    13. 13 if(id == 0)
    14. 14 {
    15. 15 printf("我是子进程\n\n");
    16. 16 execl(path,"ls","-a","-l",NULL);//调用exec函数执行其它程序
    17. 17
    18. 18 exit(1);
    19. 19 }
    20. 20 else
    21. 21 {
    22. 22 int status = 0;
    23. 23 pid_t ret = waitpid(id,&status,0);//阻塞式等待
    24. 24
    25. 25 if(ret > 0)
    26. 26 {
    27. 27 printf("\n我是父进程,等待成功,退出码:%d\n",WEXITSTATUS(status));
    28. 28 }
    29. 29 }
    30. 30
    31. 31
    32. 32 return 0;
    33. 33 }

    效果:


    2.int execlp(const char *file, const char *arg, ...);

    和上面基本一致,就是可以不用写绝对路径,path:环境变量,OS能直接找到


    3.int execv(const char *path, char *const argv[]);

    同上,只用把要执行的命令写入字符指针数组内,然后传递这个数组就行(最后一个参数必须是NULL)


    4.int execvp(const char *file, char *const argv[]);

    同上不用带绝对路径

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

    这个可以给其它程序传递环境变量

    --我自己设置了一个环境变量val

    --调用execle函数

    --mycmd.c  可以获取val这个环境变量

    --效果:

    4.3为什么要有程序替换

    1.场景需要

    一定和应用场景有关, 我们有时候, 必须让子进程执行新的程序 !!!

    例如:1.如何执行其他或我自己写的C. C++二进制程序2.如何执行其它语言的程序

    1.如何执行其他或我自己写的C. C++二进制程序

    --makefile:

    --mycmd.c

    --exec.c

    --效果:

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

    --效果:

    2.补充:为什么要创建子进程

    为什么要创建子进程

    1.如果不创建, 那么进程替换的就是父进程,创建了,替换的就是子进程,而不影响父进程(进程的独立性,且加载属于改写了,由于写时拷贝的特点,会在物理内存上开辟一块新的内存,把新的代码加载到上面,并通过页表,改变映射关系)

    2.我们想要父进程聚焦于读取数据,解析数据,指派进程进程执行代码的功能

    (父进程读取分析数据,子进程执行代码的功能)

    5.总结,实现简单的shell

    链接:shell的模拟实现

  • 相关阅读:
    elasticsearch高级功能之跨集群复制CCR
    【21天学习挑战赛】—Java编程进阶之路(4)
    CycloneDDS配置详细说明中文版(二)
    [附源码]java毕业设计高考志愿填报系统
    阅读芯片源码(RTL)
    C4D基础认识
    01_SpringMVC介绍
    Mac版eclipse如何安装,运行bpmn文件
    【心理学·人物】第二期(学术X综艺)
    12.神经网络模型
  • 原文地址:https://blog.csdn.net/m0_70402487/article/details/133502422