• Linux基础内容(13)—— 进程控制


    目录

    1.fork函数的进程创建

    1.fork返回值

    2.fork返回值

    3.fork调用失败

     2.写时拷贝

    3.退出码的知识

    4.进程退出

    1.退出的情况

    2.正常退出

    5.进程等待

    1.调用系统等待函数杀死僵尸进程

    2.僵尸状态与PCB的关系

    3.进程阻塞等待与非阻塞等待方式

    6.进程程序替换

    1.替换程序的可行性

    2.exec*函数 

    3.exec函数执行原理

    4.应用


    接上面内容

    Linux基础内容(12)—— 程序地址空间https://blog.csdn.net/m0_63488627/article/details/127909641?spm=1001.2014.3001.5501


    1.fork函数的进程创建

    1.fork返回值

    fork调用后得到两个进程:父进程和子进程;父进程的数据结构内容复制给子进程。

    2.fork返回值

    首先操作系统开发时就设置了函数的返回值,规定父进程返回子进程的pid,子进程返回0。

    此后fork函数调用,在用户层面我们调用了fork函数,其实fork的调用,是让操作系统调用系统中创建子进程的函数运行,该函数负责拷贝父进程的数据结构内容和页表,然后将子进程也并入进程中,使得fork函数调用中就将子进程创建。最后返回pid也就实现了父子进程的分离。此时父子进程正式分别运行。

    意味着子进程的创立,返回两个pid的值也就是说fork后,子进程复制了父进程的数据结构内容,随后进行了写时拷贝。二者共享父进程的代码,但是由于fork的反回值的不同,可能会进行执行部分代码的实现。

    3.fork调用失败

    进程太多。已经超过用户进程管理的限制。


     2.写时拷贝

    由于fork生成的子进程其实基底是复制拷贝父进程的数据结构内容,所以只要他们中的内容没有被修改,他们虚拟地址指向物理地址的单元是一样的。但是修改后,谁修改谁发生写时拷贝,也就是说修改的进程的页表映射的物理地址不再指向同一个单元,而是重新开辟一个空间,使得两个进程不互相影响。


    3.退出码的知识

    return 0:在操作系统看来,其实返回的值是退出码;用于标定进程执行结果是否正确。

    $?:返回最近一次执行的进程返回的退出码。

    所以,如果我们不关心main的返回值,我们直接返回0;如果关系进程的退出码,要返回特定的数据表明特定的错误。退出码意义:0成功;非0失败,不同的数表达不同的错误,需要被解释。


    4.进程退出

    1.退出的情况

    进程结束无非三种情况:

    1.运行结束,结果成功

    2.运行结束,结果失败

    3.运行异常,终止

    2.正常退出

    1.从main函数return返回退出

    2.任意地方调用exit(code)

    3._exit退出

    exit与_exit的退出进程的操作基本一致。但是他们还是有区别的:

    1.exit是C库中的函数调用;_exit是操作系统提供的调用。exit的底层实现其实是_exit;

    2.exit终止进程,主动刷新缓冲区;_exit终止进程,不会刷新缓冲区

    缓冲区在用户级的缓冲区。

    5.进程等待

    1.调用系统等待函数杀死僵尸进程

    进程在运行完后,还不能直接结束。它进程是需要等待操作系统帮他杀掉的,因为操作系统要清楚进程的状态和执行任务的完成情况。所以进程等待的目的也是为了:1.回收子进程资源,以达到杀死进程的效果2.获取子进程的退出信息,得到任务的完成状态

    wait()函数调用:

    调用该函数,会让执行该进程的处在僵尸状态的子进程结束进程

    1. #include
    2. #include
    3. #include
    4. #include
    5. #include
    6. int main()
    7. {
    8. pid_t id = fork();
    9. if(id < 0)
    10. {
    11. printf("fork error\n");
    12. return 1;
    13. }
    14. else if(id == 0)
    15. {
    16. int cnt = 10;
    17. while(cnt)
    18. {
    19. printf("我是子进程,pid:%d,ppid:%d,cnt: %d\n",getpid(),getppid(),cnt--);
    20. sleep(1);
    21. }
    22. exit(0);
    23. }
    24. else
    25. {
    26. sleep(15);
    27. pid_t ret = wait(NULL);
    28. if(ret>0)
    29. {
    30. printf("wait suxxess:%d\n",ret);
    31. }
    32. }
    33. sleep(5);
    34. return 0;
    35. }

    waitpid()函数调用:

    输入进程pid,进程接受的退出码,状态

    调用该函数,会让执行该进程的处在僵尸状态的子进程结束进程,同时status返回退出码。

    此外status有自己的位图结构。status只看前面十六位确定状态。

     异常的信号可通过kill -l可查询 

    1. #include
    2. #include
    3. #include
    4. #include
    5. #include
    6. int main()
    7. {
    8. pid_t id = fork();
    9. if(id < 0)
    10. {
    11. printf("fork error\n");
    12. return 1;
    13. }
    14. else if(id == 0)
    15. {
    16. int cnt = 5;
    17. while(cnt)
    18. {
    19. printf("我是子进程,pid:%d,ppid:%d,cnt: %d\n",getpid(),getppid(),cnt--);
    20. sleep(1);
    21. }
    22. exit(10);
    23. }
    24. else
    25. {
    26. int status
    27. pid_t ret = waitpid(id,&status,0);
    28. if(ret>0)
    29. {
    30. printf("wait suxxess:%d,sig number: %d,child exit code: %d\n",ret,(status & 0x7F),(status>>8)&0xFF);
    31. }
    32. }
    33. sleep(5);
    34. return 0;
    35. }

     如果运行异常,sig number输出异常信号,child exit code输出0(0是没有意义的意思)

    2.僵尸状态与PCB的关系

    僵尸状态下,进程几乎没有内存占着,它执行结束已经可以把创造的变量释放。但是唯独它的状态(退出信号)和任务完成与否信息(退出码)需要保存,而这些保存在PCB中,也就是说PCB在僵尸状态下不能被释放。而进程等待后时要把僵尸进程的PCB里的退出码和退出信号传给父进程中。

    WIFEXITED(status):子进程正常终止,则返回真

    WEXITSTATUS(status):若子进程正常终止,返回退出码

    3.进程阻塞等待与非阻塞等待方式

    阻塞等待:父进程等待子进程变为僵尸进程后,才将子进程PCB中的退出码等拿到,并且进行子进程的退出,在进行父进程的执行

    非阻塞等待:确认子进程的状态,父进程不会等待子进程,如果子进程僵尸就释放子进程,没有,父进程依旧退出执行自己接下来的代码。

    轮询检测:不断的进行非阻塞等待,确认子进程状态。

    waitpid的第三个参数为WNOHANG便是非阻塞。

    1. #include
    2. #include
    3. #include
    4. #include
    5. #include
    6. int main()
    7. {
    8. pid_t id = fork();
    9. if(id < 0)
    10. {
    11. printf("fork error\n");
    12. return 1;
    13. }
    14. else if(id == 0)
    15. {
    16. int cnt = 5;
    17. while(cnt)
    18. {
    19. printf("我是子进程,pid:%d,ppid:%d,cnt: %d\n",getpid(),getppid(),cnt--);
    20. sleep(1);
    21. }
    22. exit(10);
    23. }
    24. int status = 0;
    25. while(1)
    26. {
    27. pid_t ret = waitpid(id,&status,WNOHANG);
    28. if(ret==0)
    29. {
    30. //函数调用成功,子进程没有退出
    31. printf("wait done,but child is running");
    32. }
    33. else if(ret>0)
    34. {
    35. //函数调用成功,子进程退出
    36. printf("wait success\n");
    37. printf("wait suxxess:%d,sig number: %d,child exit code: %d\n",ret,(status & 0x7F),(status>>8)&0xFF);
    38. break;
    39. }
    40. else
    41. {
    42. //函数调用失败
    43. printf("waitpid code file\n");
    44. break;
    45. }
    46. }
    47. sleep(5);
    48. return 0;
    49. }

    使用非阻塞等待的好处:非阻塞等待时,父进程不会被占用所有的时间用于等待子进程,它可以处理其他的任务。

    6.进程程序替换

    1.替换程序的可行性

    以上我们讨论的子进程都是用来处理父进程复制的代码。也就是说,该种子进程的功能是为了让子进程执行父进程的一部分。那么我们也可以用子进程执行另外一个程序。 

    2.exec*函数 

    execl:找指定的程序路径,将程序加载到内存中,并且执行

    path:找到对应的进程路径;

    arg:执行什么操作;

    ... :可变参数列表,不写要以NULL结尾;

    通过调用该函数,进程可以运行另外的程序;如果出错,返回-1。

    execlp:找指定的程序名加载到内存中,并且执行

    file:在环境变量里找需要执行的程序名,系统会自动找;

    execlv:将程序的所有的执行参数一起执行

    arg[]:指令不需要分开写;

    execle:传入自定义环境变量

    envp[]:里面是自定义的环境变量,用于替换的程序之中;

    如果输出错误,直接执行原本程序的下面代码

    如果是操作系统的指令,只需符合上面的操作即可;

    如果是自己写的可执行文件,就不能用execlp了因为没在环境变量里,使用execl用路径查找;什么语言都可以调用。

    演示: 

      

    3.exec函数执行原理

    调用时,其实内存中就是对进程的数据和代码进行管理。那么execl函数便是使磁盘中对应的文件的数据和代码覆盖到内存中,必要时改变一下页表。以达到程序的替换。进程替换没有创建新的进程,只是把当前的进程内容取而代之,进行执行。

    如果是父子进程的形式,子进程执行其他程序,那么子进程不是复制一份数据结构内容这么简单,它会使得整个映射失效,重新建立属于程序的映射。

    4.模拟shell应用

    1. #include
    2. #include
    3. #include
    4. #include
    5. #include
    6. #include
    7. #include
    8. #include
    9. #define NUM 1024
    10. #define OPT_NUM 64
    11. char lineCommand[NUM];
    12. char* myargv[OPT_NUM];
    13. int main()
    14. {
    15. while (1) {
    16. //打印输出提示符
    17. printf("用户名@主机名 当前路径# ");
    18. fflush(stdout);
    19. //获取用户输入,
    20. char* s = fgets(lineCommand, sizeof(lineCommand) - 1, stdin);
    21. assert(s != NULL);
    22. (void)s;
    23. //清楚最后一个/n;
    24. lineCommand[strlen(lineCommand) - 1] = 0;
    25. //切割字符串
    26. myargv[0] = strtok(lineCommand, " ");
    27. int i = 1;
    28. while (myargv[i++] = strtok(NULL, " "));
    29. #ifdef DEBUG
    30. for (int j = 0; myargv[j]; j++)
    31. {
    32. printf("myargv[%d]:%s\n", j, myargv[j]);
    33. }
    34. #endif
    35. pid_t id = fork();
    36. assert(id != -1);
    37. if (id == 0)
    38. {
    39. execvp(myargv[0], myargv);
    40. exit(1);
    41. }
    42. waitpid(id, NULL, 0);
    43. }
    44. }
  • 相关阅读:
    【React】React18.2.0核心源码解读
    Centos7安装mysql8教程
    纯 CSS 实现瀑布流布局的方法
    一篇快速教你如何创建专业级数据可视化库
    代理模式(Proxy Pattern)
    C++ 八进制、十进制、十六进制在输出的转换
    【毕业设计】 基于Stm32的家庭智能监控系统 - 单片机 图像识别 人体检测 AI
    【Elasticsearch】Elasticsearch环境搭建
    NVIDIA Jetson测试安装yolox过程记录
    5年迭代5次,抖音推荐系统演进历程
  • 原文地址:https://blog.csdn.net/m0_63488627/article/details/127932919