操作系统——进程控制

创建进程

fork

 fork是一个系统调用函数,用来创建子进程,通过多个执行流完成任务。子进程和父进程共用一份代码,子进程数据使用写时拷贝,即子进程数据在创建的时候和父进程相同,但是当要修改数据的时候,子进程数据会再复制一份数据。

a73dde8b1dc54ed3ac9fbb1f44ce2a3a.png

函数无参,返回值pid_t是用有符号整形封装的。d53bcb1d155c48af9ca1a5bf4dbc7885.png

函数返回值,如果创建成功,父进程就返回子进程的pid,子进程返回0,如果创建失败就返回-1

循环创建多个进程

  1 #include <stdlib.h>
  2 #include <stdio.h>
  3 #include <unistd.h>
  4 
  5 void run()
  6 {
  7     while(1)
  8     {
  9         printf("child pid:%d\n",getpid());
 10         sleep(1);
 11     }
 12 }
 13 
 14 int main()
 15 {
 16     pid_t id;
 17     int i;
 18     for(i = 0; i < 5; i++)
 19     {
 20         id = fork();
 21         if(id == 0)
 22         {
 23             run();
 24             exit(0);
 25         }
 26     }
 27     sleep(1000);
 28     return 0;
 29 }                             

终止进程

进程终止有三种情况:

1.结果正确,返回

2.结果错误,返回

3.程序异常

进程运行结束返回值可以使用return,exit(),_exit()。程序异常一般是外部发给程序一个信号,程序异常终止

正常终止

return,exit(),_exit()三者的区别是什么?

return和exit()

在main函数中,return和exit都可以让进程结束;在一般函数中,return只是让函数返回,继续运行,而exit会直接终止进程

f60bf782850c43f080021cf835dd6c9a.png

9f2f245f1b9b4ea9b5f1af0a4edf40b4.png

6c048b61a42b436da7f58029850cba79.png

f6af3a1dfc7544969b765280faa41928.png

 exit()和_exit()

_exit是系统调用接口,直接终止程序;exit调用了_exit,并且会先刷新缓冲区做一些清理工作。

5a56af0662f441c3a2e10fea6ff93d12.png

9cf55964e4d04a71b7c9bce6eaf04856.png

2858fb99815b49c08c99690a953b332c.png

d1ce642ba078476193a38400e5c34b1e.png

因为printf内容不加\n不会刷新缓冲区,不会打印出来

异常终止

写一个死循环让程序一直运行

e9cfd45ed9684afeae72a18c348e9875.png

68aefe9ffc0049ee9155823cd52487df.png

这里向程序发送Floating Point exception异常信号 

56ad9432f3624c7f8bc1f8e35be342d2.png

程序收到信号后,异常终止

进程等待

当子进程还没有退出,父进程就要退出时,如果父进程不等待子进程直接退出,就会让子进程变为僵尸进程造成内存泄漏。因此我们需要用进程等待的方式获取子进程的运行状态以及退出码。

介绍两个系统调用接口:

wait和waitpid

waitpid的功能包含了wait,我们先说waitpid

#include <sys/wait.h>
pid_t waitpid(pid_t pid, int *status, int options);

pid表示子进程的pid:
-1表示等待任何子进程,和wait一样后面讲

>0表示等待子进程为pid的进程

status是一个输出型参数:
int有32bit,前16位是有效位。前七位是终止信号,如果程序异常终止就会将信号保存到这里;第八位是core dump标志;8-16位是退出码

WIFEXITED(status)可以判断子进程是否正常结束

  • 如果子进程正常结束(通过 exit 调用或从主函数返回),WIFEXITED 返回非零值(真)
  • 如果子进程是由于其他原因结束(如信号),则返回零值(假)

WEXITSTATUS(status)可以用来获取子进程传递给 exit() 的实际退出值

option是用来修改waitpid行为的选项

没传参时默认为阻塞轮询,即父进程必须等待子进程结束才继续执行

传 WNOHANG 表示非阻塞轮询,父进程获取子进程状态后不论子进程是否结束都会先执行自己的代码。如果子进程结束返回子进程pid;如果未结束返回0

返回值

>0表示子进程正常结束,返回子进程pid

0 表示子进程还未结束

<0表示子进程异常结束

 

阻塞轮询:

  1 #include <stdio.h>
  2 #include <unistd.h>
  3 #include <sys/types.h>
  4 #include <sys/wait.h>
  5 #include <stdlib.h>
  6 
  7 int main()
  8 {
  9     for(int i = 0; i < 5; i++)
 10     {
 11         pid_t id = fork();
 12         if(id == 0)
 13         {
 14             printf("child pid:%d\n", getpid());
 15             sleep(1);
 16             exit(0);
 17         }
 18     }
 19 
 20     sleep(5);
 21                                                                        
 22     int status = 0;
 23     for(int i = 0; i < 5; i++)
 24     {
 25         pid_t ret = waitpid(-1, &status, 0);
 26         if(ret > 0)
 27         {
 28             printf("wait success pid:%d\n", ret);
 29             sleep(1);
 30         }
 31     }
 32     return 0;
 33     sleep(5);
 34 }

运行结果 

bca53728d3254f1c95c8551935332933.png

运行过程的进程监视 

074a396c24a34ca3b5235664695d972e.png

非阻塞轮询: 

  1 #include <stdio.h>
  2 #include <unistd.h>
  3 #include <sys/types.h>
  4 #include <sys/wait.h>
  5 #include <stdlib.h>
  6 #include <errno.h>
  7 #include <string.h>
  8 
  9 int main()
 10 {
 11     //非阻塞轮询
 12     
 13     pid_t id = fork();
 14     if(id == 0)
 15     {
 16         //child
 17         printf("child pid:%d, ppid:%d\n", getpid(), getppid());
 18         //测试异常
 19         //int* p = NULL;
 20         //*p = 1;
 21         sleep(10);
 22         exit(1);
 23     }              
 24     else if(id > 0)                                  
 25     {                    
 26         int status;
 27         //father                                     
 28         while(1)         
 29         {                                                                                  
 30             pid_t ret = waitpid(id, &status,WNOHANG);                           
 31             if(ret == id) 
 32             {
 33                 if(WIFEXITED(status))                                           
 34                 {           
 35                     //正常结束
 36                     printf("wait success, exit code:%d\n", WEXITSTATUS(status));
 37                     break;                                       
 38                 }     
 39             }         
 40             else if(ret < 0)                                     
 41             {         
 42                 //异常          
 43                 printf("wait fail,%s\n",strerror(status & 0x7f));
 44                 break;   
 45             }                   
 46             else                                        
 47             {                                                                              
 48                 //子进程还在执行
 49                 printf("child running,wait a minute\n");
 50                 sleep(1);
 51             }
 52         }
 53     }

正常结束 

 1319b15ba6c245aeb96428fa296f73c0.png

异常结束 

0e64830d36e74df491de47f81cab6562.png

进程替换

原理

cb668a3f296a4db0982591496cc87925.png 

程序加载到内存时,操作系统创建PCB,页表建立虚拟内存的映射,当程序出现进程替换时,用来替换的程序将数据和代码加载到当前运行程序的地址上,mm_struct也会进行调整,重新建立映射,之后程序从第一行开始运行。这个过程就像小说里的夺舍,主人公的躯体没有变但是灵魂是别人的那样。

 进程替换的接口——exec*

80854975f1c74d5195434b55f0e9edae.png

还有一个接口时execve,这个是系统调用接口,上面的六个接口都是封装了系统调用接口实现的。

用法

根据exec后面的字母,传递的参数不同

l&&v

l表示命令行参数是采用列表(list)一个个传递给函数的;v表示命令行参数是由一个存储命令的字符串数组(vector)传递的

p

不加p,函数的第一个参数是程序的绝对路径;加p,函数第一个参数可以不是绝对路径,会从PATH环境变量自动寻找。

e

不加e,替换的程序使用当前程序的环境变量;加e,替换的程序用传递的环境变量覆盖原有的环境变量

传递命令行参数,必须以NULL结尾,包括argv数组,最后一个元素必须是NULL

使用例子

1.

  1 #include <iostream>
  2 #include <unistd.h>
  3 
  4 using namespace std;
  5 int main()
  6 {
  7     cout << "process begin" << endl;
  8     execlp("ls","ls","-a","-l",NULL);
  9     cout << "process end" << endl;
 10     return 0;
 11 }  

a9ff04fb431e4d5b9d6079a02f2b57ab.png

运行结果没有“process end”,说明程序替换后,不会执行旧程序的后面代码 

2.用自己写的程序替换

//exec.cpp                                                                                                                              
    1 #include <iostream>
    2 #include <unistd.h>
    3 using namespace std;
    4 
    5 //extern char** environ;
    6 int main()
    7 {
    8     char* const env[] =
    9     {
   10         "path=1234",
   11         "eilse",
   12         NULL
   13     };                                                                                                                                              
   14     char* const myargv[] = {
   15         "ordertest",
   16         "-a",
   17         "-b",
   18         "-c",
   19         NULL
   20     };
   21     cout << "process begin" << endl;
   22     execve("./myproc", myargv,env);
   23     //execve("./myproc", myargv,environ);
   24     return 1;
   25 }


// myproc.cpp                                                                                                                            
    1 #include <iostream>
    2 #include <unistd.h>
    3 using namespace std;
    4 int main(int argc, char* argv[], char* env[])
    5 {
    6     cout << "running" << endl;
    7     cout << "Command:" << endl;
    8     for(int i = 0; argv[i]; i++)
    9     {
   10         cout << argv[i] << ' ';
   11     }
   12     cout << endl;
   13 
   14     cout << "Environ:" << endl;
   15     for(int i = 0; env[i]; i++)
   16     {
   17         cout << env[i] << endl;
   18     }
   19     cout << "ending" <<endl;                                                                                                                        
   20 
   21 }

8cfde78a8d6d40519def1f643519da5f.png 

 

 

  • 23
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值