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.特点:

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

Linux: 进程(控制)_第1张图片

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

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

1.3为什么要写实拷贝?

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

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

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

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

2.进程的终止

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

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

2.2进程常见的退出方式

a. 代码跑完, 结果正确

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

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

Linux: 进程(控制)_第2张图片

2.3进程常见的退出方法

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

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

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

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

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

Linux: 进程(控制)_第3张图片

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

_exit直接退出

3.进程的等待

3.1为什么进行进程等待

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

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

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

Linux: 进程(控制)_第4张图片

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:

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

效果:

Linux: 进程(控制)_第6张图片

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

效果:

Linux: 进程(控制)_第7张图片

4.进程的替换

4.1概念以及原理

1.概念

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

Linux: 进程(控制)_第8张图片

2.原理

Linux: 进程(控制)_第9张图片

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

效果:

Linux: 进程(控制)_第10张图片


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这个环境变量

Linux: 进程(控制)_第11张图片

--效果:

Linux: 进程(控制)_第12张图片

4.3为什么要有程序替换

1.场景需要

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

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

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

--makefile:

Linux: 进程(控制)_第13张图片

--mycmd.c

Linux: 进程(控制)_第14张图片

--exec.c

--效果:

Linux: 进程(控制)_第15张图片

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

--效果:

Linux: 进程(控制)_第16张图片

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

为什么要创建子进程

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

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

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

你可能感兴趣的:(Linux,linux,运维,服务器)