• Linux进程控制


    一、进程终止

    0x01 进程退出场景

    ①代码运行完毕,结果正确,退出码为0
    ②代码运行完毕,结果不正确,退出码!0,但是有多种可能
    ③代码异常终止

    0x02 退出码

    1. #include
    2. int main()
    3. {
    4. printf("hello world\n");
    5. return 123;
    6. }

     ①echo $?输出最近一次进程退出时的退出码
     ②第一次输出的退出码123,是main函数的return值
     ③第二次就是正常的退出码

    0x03 退出码对应的错误原因

    1. #include<stdio.h>
    2. #include<string.h>
    3. int main()
    4. {
    5. for(int i = 0;i < 100;i++)
    6. {
    7. printf("%d: %s\n",i,strerror(i));
    8. }
    9. return 0;
    10. }

    0x04 代码异常终止

    1. #include<stdio.h>
    2. #include<string.h>
    3. int main()
    4. {
    5. int a = 10;
    6. a /= 0;
    7. for(int i = 0;i < 100;i++)
    8. {
    9. printf("%d: %s\n",i,strerror(i));
    10. }
    11. return 0;
    12. }

    此时,相当于vs中的程序奔溃,退出码也就变得没有意义了 

    0x05 进程退出的方式

     ①main函数return,代表进程退出
     ②exit在任意地方调用,都代表终止进程,参数是退出码

    1. #include
    2. int fun()
    3. {
    4. exit(123);
    5. printf("hello world\n");
    6. return 1;
    7. }
    8. int main()
    9. {
    10. fun();
    11. for(int i = 0;i < 100;i++)
    12. {
    13. printf("%d: %s\n",i,strerror(i));
    14. }
    15. return 0;
    16. }

     

    1. #include<stdio.h>
    2. #include<string.h>
    3. #include<stdlib.h>
    4. #include<unistd.h>
    5. int main()
    6. {
    7. printf("hello world");//数据时被暂时保存在输出缓冲区中
    8. sleep(4);
    9. exit(EXIT_SUCCESS); //exit 或者main return 本身就会要求系统进行缓冲区刷新
    10. //return 0;
    11. }
    12. //运行代码
    13. [wh@bogon lesson11]$ ./myproc
    14. hello world[wh@bogon lesson11]$

    ③_exit终止进程,强制终止进程,不会进行进程的后续收尾工作,比如刷新缓冲区(用户级缓冲区)

    1. #include<stdio.h>
    2. #include<string.h>
    3. #include<stdlib.h>
    4. #include<unistd.h>
    5. int main()
    6. {
    7. printf("hello world");
    8. sleep(4);
    9. _exit(12);
    10. // exit(EXIT_SUCCESS);
    11. //return 0;
    12. }

    ④_exit()与exit的区别  

    0x06 进程退出,操作系统层面做了什么?

     系统层面,少了一个进程,释放了 PCB,mm_struct,页表和各种映射关系,代码,数据和申请的空间 

    二、进程等待

    0x01 进程等待是什么

    fork()之后,子进程是为了帮助父进程完成某种任务,而父进程要知道子进程做的怎么样,所以让父进程fork()之后,需要通过wait/waitpid等待子进程退出信息

    0x02 为什么要让父进程等待呢?

    ①通过获取子进程退出信息,能够得知子进程的执行结果
    ②可以保证时序的问题,子进程先退出,父进程后退出
    ③进程退出的时候会先进入僵尸状态,会造成内存泄漏的问题,需要通过父进程wait,释放该子进程占用的资源

    0x03 如何进行进程等待?

     方法一 wait方法:

    #include
    #include
    pid_t wait(int*status);

    返回值: 成功返回被等待进程pid,失败返回-1。

    参数: 输出型参数,获取子进程退出状态,不关心则可以设置成为NULL

    1. #include<stdio.h>
    2. #include<string.h>
    3. #include<stdlib.h>
    4. #include<unistd.h>
    5. #include<sys/types.h>
    6. #include<sys/wait.h>
    7. int main()
    8. {
    9. pid_t id = fork();
    10. if(id == 0)
    11. {
    12. int cnt = 5;
    13. while(cnt)
    14. {
    15. printf("child[%d] is running: cnt is : %d\n",getpid() ,cnt);
    16. cnt--;
    17. sleep(1);
    18. }
    19. exit(0);
    20. }
    21. sleep(10);
    22. pid_t ret = wait(NULL);
    23. if(ret > 0)
    24. {
    25. printf("parent wait: %d,success\n",ret);
    26. }
    27. else
    28. {
    29. printf("parent wait failed\n");
    30. }
    31. sleep(10);
    32. }

     

     子进程先执行5s,然后退出,父进程也同时执行5s,此时因为子进程已经退出,所以是僵尸进程,再5s之后,父进程等待成功,释放子进程的资源,然后又休眠10s,父进程进入睡眠状态,之后父进程结束

    方法二 waitpid方法:  

    pid_t ret = waitpid(id,NULL,0);  //等待执行一个进程
    pid_t ret = waitpid(-1,NULL,0);  //等待任意一个子进程,等价于wait
    pid_t waitpid(pid_t pid, int *status, int options);

    返回值:

    pid > 0: 返回子进程的pid
    pid < 0: 等待失败

    参数1:  

    pid:
    pid = -1,表示等待任意一个子进程,等价于wait
    pid > 0,等待指定的子进程

    参数2:

    status:
    ①是一个输出型参数,有操作系统填充
    ②如果传递NULL.表示不关心子进程的退出状态信息
    ③否则,操作系统会根据该参数,将子进程的退出信息反馈给父进程
    ④父进程拿到的status结果,一定和子进程如何退出强相关
    ⑤子进程的退出结果,共有三种,代码异常终止则会收到某种信号,如果没有收到信号说明没有问题,之后就会返回退出码给父进程,最终会让父进程通过status得到子进程执行的结果

    ①status表示的共32bit,但是高16bit没有用到,只使用低16个bit
    ②低8bit:表示某种信号,如果收到这种信号,则表示异常终止,未收到则表示正常运行,一般情况下值为0,但是这8bit也只用到了前7bit
    ③高8bit:表示子进程返回的退出状态  

     查看status所收到的信息:

    1. #include<string.h>
    2. #include<stdlib.h>
    3. #include<unistd.h>
    4. #include<sys/types.h>
    5. #include<sys/wait.h>
    6. int main()
    7. {
    8. pid_t id = fork();
    9. if(id == 0)
    10. {
    11. int cnt = 5;
    12. while(cnt)
    13. {
    14. printf("child[%d] is running: cnt is : %d\n",getpid() ,cnt);
    15. cnt--;
    16. sleep(1);
    17. }
    18. exit(12);
    19. }
    20. int status = 0;
    21. pid_t ret = waitpid(id,&status,0);
    22. if(ret > 0)
    23. {
    24. printf("parent wait: %d,success,status exit code: %d,status exit signal: %d\n",ret,(status>>8)&0xFF,status&0x7F);
    25. //status exit code:因为是高8位,所以先右移8位,然后与0xFF按位与
    26. //status exit signal:因为是低8位,但是只用到了前7位,所以与0x7F按位与
    27. }
    28. else
    29. {
    30. printf("parent wait failed\n");
    31. } }

    status收到的退出码为12,终止信号为0,则表示代码跑完,但结果不正确  

    当然还有俩种情况分别是:

    ①代码运行完毕,结果正确,比如status收到的退出码为0,并且正常运行 

     ②代码异常终止:比如代码没有跑完,但是子进程被干掉,此时会收到终止信号,并且退出码已经不重要了

     当然想要获取退出码还有一种方式:

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

    1. #include<string.h>
    2. #include<stdlib.h>
    3. #include<unistd.h>
    4. #include<sys/types.h>
    5. #include<sys/wait.h>
    6. int main()
    7. {
    8. pid_t id = fork();
    9. if(id == 0)
    10. {
    11. int cnt = 5;
    12. while(cnt)
    13. {
    14. printf("child[%d] is running: cnt is : %d\n",getpid() ,cnt);
    15. cnt--;
    16. sleep(1);
    17. }
    18. exit(12);
    19. }
    20. int status = 0;
    21. pid_t ret = waitpid(id,&status,0);
    22. if(ret > 0)
    23. {
    24. if(WIFEXITED(status))//没有收到任何终止信号
    25. {
    26. printf("exit code: %d\n",WEXITSTATUS(status));//正常结束,获取对应的退出码
    27. }
    28. }
    29. }

     参数3:

    options:
    0:默认行为,表示阻塞等待,子进程没返回,父进程什么也没做,一直等子进程 WNOHANG: 设置等待方式为非阻塞等待

    阻塞等待: 张三找李四跑步,李四在学习,让张三等30分钟,张三此时让李四电话不要挂,通过电话知道李四的状态,等学习完在电话中说一声再挂电话,李四不学完,张三不走,主打一个死等,即子进程不退出,父进程不返回
    非阻塞等待: 张三找李四跑步,李四在学习,让张三等30分钟,张三2分钟一个电话,检测李四学没学完,不会因为李四这次没学完而把自己卡住,一次等待可能需要多次检测,是基于非阻塞等待的轮询方案  


    非阻塞等待:
    ①如果子进程已经退出,调用wait/waitpid时,wait/waitpid会立即返回,并且释放资源,获得子进程退出信息。
    ②如果在任意时刻调用wait/waitpid,子进程存在且正常运行,则进程可能阻塞。 如果不存在该子进程,则立即出错返回。 

    阻塞等待了是不是意味着父进程不被调度了?  

    阻塞本质:进程的PCB被放入了等待队列,并将进程的状态改为S状态
    返回的本质:进程的PCB从等待队列拿到运行队列,从而被CPU调度 

    基于非阻塞的轮询方案:  

    1. #include<stdio.h>
    2. #include<string.h>
    3. #include<stdlib.h>
    4. #include<unistd.h>
    5. #include<sys/types.h>
    6. #include<sys/wait.h>
    7. int main()
    8. {
    9. pid_t id = fork();
    10. if(id == 0)
    11. {
    12. int cnt = 10;
    13. while(cnt)
    14. {
    15. printf("child[%d] is running: cnt is : %d\n",getpid() ,cnt);
    16. cnt--;
    17. sleep(1);
    18. }
    19. exit(12);
    20. }
    21. int status = 0;
    22. while(true)
    23. {
    24. pid_t ret = waitpid(id,&status,WNOHANG);
    25. if(ret == 0)
    26. {
    27. //子进程没有退出,但waitpid等待成功,需要父进程重复进行等待
    28. printf("Do parent things\n");
    29. }
    30. else if(ret > 0)
    31. {
    32. //子进程退出,waitpid也成功等待,获取到对应的结果
    33. printf("parent wait: %d,success,status exit code: %d,status exit signal: %d\n",ret,(status>>8)&0xFF,status&0x7F);
    34. break;
    35. }
    36. else{
    37. //ret < 0
    38. //等待结果
    39. perror("waitpid");
    40. break;
    41. }
    42. sleep(1);
    43. }
    44. }

    父进程没有等到子进程的退出信息,所以一直在做自己的事情,等子进程退出时,父进程成功等待,获得了子进程的退出信息 

    三、程序替换

    0x01 什么叫程序替换?

    进程不变,仅仅替换当前进程的代码和数据的技术

    0x02 程序替换例子

    1. #include
    2. #include
    3. #include
    4. #include
    5. #include
    6. int main()
    7. {
    8. printf("I am a process,pid: %d\n",getpid());
    9. execl("/usr/bin/ls","ls","-a","-l",NULL);
    10. printf("hello world\n");
    11. }

    此时就会有一个问题,为什么"hello world"没有被执行呢? 而且第一句printf却被执行了呢?

    因为"hello world"被execl进程了程序替换,而第一句printf是先执行,之后才进行的程序替换

    0x03 程序替换的本质

    程序替换的本质就是把程序的进程代码+数据,加载进特定进程的上下文中
    通俗的讲,比如:C/C++程序要运行,必须先加载到内存中,那么如何加载呢?是通过加载器,也就可以理解为通过exec系列的程序替换函数将程序从磁盘加载到内存

    0x04 创建子进程的目的

    之前我们创建子进程的目的即是让子进程执行父进程代码的一部分,但是如果我们想让子进程执行一个全新的程序呢?

    此时用到的即是程序替换

    1. #include
    2. #include
    3. #include
    4. #include
    5. #include
    6. #include
    7. int main()
    8. {
    9. pid_t id = fork();
    10. if(id == 0)
    11. {
    12. printf("I am a process,pid: %d\n",getpid());
    13. execl("/usr/bin/ls","ls","-a","-l",NULL);
    14. printf("hello world\n");
    15. exit(0);
    16. }
    17. while(1)
    18. {
    19. printf("I am a parent\n");
    20. sleep(1);
    21. }
    22. //parent
    23. waitpid(id,NULL,0);
    24. printf("success\n");
    25. }

    首先执行父进程,之后子进程,子进程进行了程序替换,然后一直执行着父进程 此时我们会有一个疑问:不是说父子代码是共享的吗?为什么他们执行出的结果不一样? 因为程序替换会更改代码区的代码,此时就会发生写实拷贝,进程具有独立性,俩个进程之间互不干扰  

    0x05 exec系列函数

     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[]);//系统调用

    理解: 

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

     

    0x06 exec系列返回问题

    只要进程的程序替换成功,就不会执行后续代码,意味着exec系列函数,成功的时候,不需要返回值检测 当然只要execl系列返回了,就一定是因为调用失败了

    1. #include
    2. #include
    3. #include
    4. #include
    5. #include
    6. #include
    7. int main()
    8. {
    9. printf("I am a process,pid: %d\n",getpid());
    10. execl("/usr/bin/lsss","ls","-a","-l",NULL);
    11. printf("hello world\n");
    12. }

     

    0x07 execl

    1. #include
    2. #include
    3. #include
    4. #include
    5. #include
    6. int main()
    7. {
    8. if(fork() == 0)
    9. {
    10. printf("hello world\n");
    11. execl("/usr/bin/ls","ls","-a","-l",NULL);
    12. printf("end\n");
    13. exit(1);
    14. }
    15. pid_t ret = waitpid(-1,NULL,0);
    16. if(ret > 0)
    17. {
    18. printf("wait child success\n");
    19. }
    20. }

    0x08 execv

    1. #include<stdio.h>
    2. #include<unistd.h>
    3. #include<sys/types.h>
    4. #include<stdlib.h>
    5. #include<sys/wait.h>
    6. int main()
    7. {
    8. if(fork() == 0)
    9. {
    10. printf("hello world\n");
    11. char* argv[] = { "ls","-a","-l",NULL };
    12. execv("/usr/bin/ls",argv);
    13. printf("end\n");
    14. exit(1);
    15. }
    16. pid_t ret = waitpid(-1,NULL,0);
    17. if(ret > 0)
    18. {
    19. printf("wait child success\n");
    20. }
    21. }

     只是将execl的可变式列表变成了指针数组

    0x09 execlp

    1. #include
    2. #include
    3. #include
    4. #include
    5. #include
    6. int main()
    7. {
    8. if(fork() == 0)
    9. {
    10. printf("hello world\n");
    11. execlp("ls","ls","-a","-l","-d",NULL);
    12. printf("end\n");
    13. exit(1);
    14. }
    15. pid_t ret = waitpid(-1,NULL,0);
    16. if(ret > 0)
    17. {
    18. printf("wait child success\n");
    19. }
    20. }

    l: 表示采用列表的形式
    p:自动搜索环境变量PATH,不用再写明路径  

    0X10 execvp

     

    1. #include<stdio.h>
    2. #include<unistd.h>
    3. #include<sys/types.h>
    4. #include<stdlib.h>
    5. #include<sys/wait.h>
    6. int main()
    7. {
    8. if(fork() == 0)
    9. {
    10. printf("hello world\n");
    11. char* argv[] = { "ls","-a","-l","-d",NULL };
    12. execvp("ls",argv);
    13. printf("end\n");
    14. exit(1);
    15. }
    16. pid_t ret = waitpid(-1,NULL,0);
    17. if(ret > 0)
    18. {
    19. printf("wait child success\n");
    20. }
    21. }

    v:指针数组的形式
    p:不用指明详细路径  

    0x11 用一个程序去调用另一个程序

     

    1. //myproc.c,用这个程序去调用myexe
    2. #include
    3. #include
    4. #include
    5. #include
    6. #include
    7. int main()
    8. {
    9. if(fork() == 0)
    10. {
    11. printf("hello world\n");
    12. execl("./myexe","myexe",NULL);
    13. printf("end\n");
    14. exit(1);
    15. }
    16. pid_t ret = waitpid(-1,NULL,0);
    17. if(ret > 0)
    18. {
    19. printf("wait child success\n");
    20. }
    21. }
    22. //myexe.c
    23. #include
    24. int main()
    25. {
    26. printf("hello test\n");
    27. return 0;
    28. }

    一次形成俩个可执行程序:

    因为伪目标所以总是被执行,但是没有依赖方法,所以并不会形成all,但是有依赖方法,Makefile在执行的时候一定是想先形成的是all,先形成all,那么就要先形成myexe 和myproc,但是又没有依赖方法,并不会再形成all

    1. //Makefile
    2. .PHONY:all
    3. all:myproc myexe
    4. myproc:myproc.c
    5. gcc -o $@ $^
    6. myexe:myexe.c
    7. gcc -o $@ $^
    8. .PHONY:clean
    9. clean:
    10. rm -f myproc myexe

    0x12 execle

    1. #include<stdio.h>
    2. #include<unistd.h>
    3. #include<sys/types.h>
    4. #include<stdlib.h>
    5. #include<sys/wait.h>
    6. int main()
    7. {
    8. if(fork() == 0)
    9. {
    10. printf("hello world\n");
    11. char* env[] = {"MYENV = hello wrold","MYENV1 = HELLO WORLD",NULL};
    12. execle("./myexe","myexe",NULL,env);
    13. printf("end\n");
    14. exit(1);
    15. }
    16. pid_t ret = waitpid(-1,NULL,0);
    17. if(ret > 0)
    18. {
    19. printf("wait child success\n");
    20. }
    21. }

     

     ①当只执行./myexe的时候,输出的是系统的环境变量
     ②当通过myproc来调用myexe的时候,使用的就是我们导入的环境变量(比如上面的例子)

    0x13 execve

    1. #include<stdio.h>
    2. #include<unistd.h>
    3. #include<sys/types.h>
    4. #include<stdlib.h>
    5. #include<sys/wait.h>
    6. int main()
    7. {
    8. if(fork() == 0)
    9. {
    10. printf("hello world\n");
    11. char* env[] = {"MYENV = hello wrold","MYENV1 = HELLO WORLD",NULL};
    12. char* argv[] = {"myexe",NULL};
    13. execve("./myexe",argv,env);
    14. printf("end\n");
    15. exit(1);
    16. }
    17. pid_t ret = waitpid(-1,NULL,0);
    18. if(ret > 0)
    19. {
    20. printf("wait child success\n");
    21. }
    22. }

     e:就是自己添加环境变量
     v:数组

    四、制作一个简易版的shell

    0x01 打印提示符

    1. #include
    2. #include
    3. #define NUM 128
    4. int main()
    5. {
    6. char command[NUM];
    7. for(;;)
    8. {
    9. command[0] = '\0';
    10. printf("[yh@Linux yh_shell]$");
    11. fflush(stdout);
    12. fgets(command,NUM,stdin);//获取命令字符串
    13. command[strlen(command)-1] = '\0';
    14. printf("echo: %s\n",command);
    15. }
    16. }

    0x02 解析命令字符串

    1. const char* sep = " ";
    2. argv[0] = strtok(command,sep);
    3. int i = 1;
    4. while(argv[i] = strtok(NULL,sep))
    5. {
    6. i++;
    7. }
    8. for(i = 0;argv[i];i++)
    9. {
    10. printf("argv[%d],%s\n",i,argv[i]);
    11. }

    0x03 执行第三方命令

    1. if(fork() == 0)
    2. {
    3. execvp(argv[0],argv);
    4. exit(1);
    5. }
    6. waitpid(-1,NULL,0);

    0x04 为什么cd ..,返回上一级路径,却没有变化?

     

     父进程路径:

     

    ①因为执行cd ..,是子进程在执行,执行回退的并非是shell,而我们是想让父进程bash去执行回退

    ②fork()要执行的命令是第三方命令,而我们想以内建的方式运行,即不创建子进程,而是让父进程shell自己执行,相当于调用了自己的一个函数

    0x05 进行修改

    chdir 是C语言中的一个系统调用函数(同cd),用于改变当前工作目录,其参数为Path 目标目录,可以是绝对目录或相对目录。(参考自百度)

    1. //检测命令是否需要shell本身执行的,内建命令
    2. if(strcmp(argv[0],"cd") == 0)
    3. {
    4. if(argv[1] != NULL)
    5. {
    6. chdir(argv[1]);
    7. continue;
    8. }
    9. }

    0x06 整体代码

    1. #include<stdio.h>
    2. #include<string.h>
    3. #include<stdlib.h>
    4. #include<sys/types.h>
    5. #include<sys/wait.h>
    6. #include<unistd.h>
    7. #define NUM 128
    8. #define CMD_NUM 64
    9. int main()
    10. {
    11. char command[NUM];
    12. for(;;)
    13. {
    14. command[0] = '\0';
    15. char* argv[CMD_NUM] = {NULL};
    16. printf("[yh@Linux yh_shell]$");
    17. fflush(stdout);
    18. fgets(command,NUM,stdin);
    19. command[strlen(command)-1] = '\0';
    20. // printf("echo: %s\n",command);
    21. const char* sep = " ";
    22. argv[0] = strtok(command,sep);
    23. int i = 1;
    24. while(argv[i] = strtok(NULL,sep))
    25. {
    26. i++;
    27. }
    28. // for(i = 0;argv[i];i++)
    29. // {
    30. // printf("argv[%d],%s\n",i,argv[i]);
    31. // }
    32. if(strcmp(argv[0],"cd") == 0)
    33. {
    34. if(argv[1] != NULL)
    35. {
    36. chdir(argv[1]);
    37. continue;
    38. }
    39. }
    40. if(fork() == 0)
    41. {
    42. execvp(argv[0],argv);
    43. exit(1);
    44. }
    45. waitpid(-1,NULL,0);
    46. }
    47. }

     

  • 相关阅读:
    【Android知识笔记】UI体系(三)
    超详细的springBoot学习笔记
    ApplicationContext的实现类有哪些呢?
    Python装饰器ZERO 2 HERO
    php安装imap扩展模块的曲折过程
    别再张口闭口高并发海量数据了,Spring这些东西都会了吗?
    初学者设计PCB,如何检查光绘文件的断头线
    telnet无效指令,telnet找不到命令
    PIE Engine系列2 数据的上传、调用及下载(附源码超详细)
    神经网络常用的训练方式,神经网络训练过程详解
  • 原文地址:https://blog.csdn.net/qq_53010164/article/details/134083421