Linux系统编程篇--进程控制篇

Posted 蚍蜉撼树谈何易

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Linux系统编程篇--进程控制篇相关的知识,希望对你有一定的参考价值。

了解fork()函数

在linux中fork函数时非常重要的函数,它从已存在进程中创建一个新进程新进程为子进程,而原进程为父进程。
头文件:
#include<unistd.h>
pid_t pd=fork();
返回值类型:
子进程中返回0,父进程返回子进程的pid,失败返回-1;

什么是子进程

子进程是以父进程为“模板”生成的一个新进程
系统调用fork,内核中做了什么?
1.分配新的内存块和内核数据结构给子进程
2.将父进程部分数据结构内容拷贝至子进程
3.添加子进程到系统进程列表当中
4.fork返回,开始调度器调度
在这里插入图片描述

fork()基本用途

1.一个父进程希望复制自己,使父子进程同时执行不同的代码段。例如,父进程等待客户端请求,生成子进程来处理请求。
2.一个进程要执行一个不同的程序。例如子进程从fork返回后,调用exec函数。

fork()失败原因

1.系统中有太多的进程
2.实际用户的进程数超过了限制

了解写时拷贝

通俗点来说,就是什么时候修改,什么时候再进行拷贝。

概念

操作系统通过fork创建子进程之后,子进程复制了父进程,一开始父子进程指向同一块物理内存空间,因此数据看起来是一模一样的。但是进程间具有独立性,因此当物理空间的数据发生改变时,子进程就会重新开辟物理空间,将数据拷贝过来。这就是写时拷贝技术。因为代码是只读的,因此父子进程数据独有,代码共享。 (当物理空间数据发生改变时)

两种方式

对数据不做修改的情况下
在这里插入图片描述

对数据做出修改
在这里插入图片描述

进程退出

进程退出情况

1.代码运行完毕,结果正确
2.代码运行完毕,结果不正确
3.代码异常终止

查看进程退出码

echo $?

进程常见的退出方法

1.main()函数return退出。

  1 #include<iostream>
  2 using namespace std;
  3 int main()
  4 {
  5     cout<<"hello Linux"<<endl;
  6     return 21;                                                                                                                                                                        
  7 }

在这里插入图片描述
可以看到我们的退出码 (main函数的return值为退出码)为21。
说一下为什么我们main()函数的退出码为什么是0?
一般在编程中,我们一般认为0是程序正确执行的返回值,通过返回值的不同确定错误的类型。
在这里插入图片描述

2.调用exit()

 #include <stdlib.h>
 void exit(int status);

//status为退出码,这个函数是来终止程序的,只要执行到该语句,程序直接退出,哪怕后面还有未执行的语句,
在这里插入图片描述
3._exit()函数

#include <unistd.h>
void _exit(int status);

status仍为退出码

回调函数

在这里插入图片描述
在这里插入图片描述

exit()函数和_exit()函数区别

1.exit()函数为库函数,_exit()为系统调用函数
2.exit()函数会比_exit多做两件事情,刷新缓冲区,执行用户自定义清理函数。

在这里插入图片描述

在这里插入图片描述
3.exit()函数内部封装了_exit();

刷新缓冲区

1.\\n刷新

 1 #include<iostream>
  2 #include<stdlib.h>
  3 using namespace std;
  4 int main()
  5 {
  6    
  7     cout<<"hello Linux";                                                                                                                                                              
  8     return 21;
  9 }

在这里插入图片描述
在这里插入图片描述

2.fflush
头文件#include<stdio.h>
fflush(file* fp);
3.exit()函数刷新
4.main()函数return回来后

进程等待

进程等待的必要性

1.之前讲过,子进程退出,父进程如果不管不顾,就可能造成‘僵尸进程’的问题,进而造成内存泄漏。
2.另外,进程一旦变成僵尸状态,那就刀枪不入,“杀人不眨眼”的kill -9 也无能为力,因为谁也没有办法杀死一个已经死去的进程。
3.最后,父进程派给子进程的任务完成的如何,我们需要知道。如,子进程运行完成,结果对还是不对,或者是否正常退出。
4.父进程通过进程等待的方式,回收子进程资源,获取子进程退出信息

1 #include<iostream>
  2 #include<stdlib.h>
  3 using namespace std;
  4 #include<unistd.h>
  5 void mycallback()
  6 {
  7     cout<<"this is my callback"<<endl;
  8 }
  9 int main()
 10 {
 11 
 12      pid_t pd=fork();
 13      if(pd<0)
 14      {
 15          cerr<<"fork failed"<<endl;
 16      }
 17      else if(pd==0)
 18      {
 19        cout<<"i am son"<<endl;
 20      }
 21      else
 22      {
 23          while(1)
 24          {
 25              cout<<" i am father"<<endl;
 26              sleep(1);                                                                                                                                                                
 27          }
 28      }
      return 0;
      }

在这里插入图片描述

进程等待,wait()函数

#include <sys/wait.h>
 pid_t wait(int *stat_loc);

 1 #include<iostream>
  2 #include<stdlib.h>
  3 using namespace std;
  4 #include<unistd.h>
  5 #include<sys/wait.h>
  6 void mycallback()
  7 {
  8     cout<<"this is my callback"<<endl;
  9 }
 10 int main()
 11 {
 12 
 13      pid_t pd=fork();
 14      if(pd<0)
 15      {
 16          cerr<<"fork failed"<<endl;
 17      }
 18      else if(pd==0)
 19      {
 20        cout<<"i am son"<<endl;
 21      }
 22      else
 23      {
 24          while(1)
 25          {
 26              wait(NULL);
 27              cout<<" i am father"<<endl;
 28              sleep(1);
 29          }
 30      }
 31     return 0;                                                                                                                                                                         
 32 } 

在这里插入图片描述
wait函数的参数:
为一个输出型参数,区分一下输入型参数与输出型参数
在这里插入图片描述
返回值
成功返回被等待进程pid,失败返回-1。
原理 :
父进程调用wait阻塞,wait是阻塞式的,父进程调用wait会被阻塞住,父进程调用wait就会一直在这里等SIGCHILD信号,收到这个信号就说明父进程要去回收僵尸进程了
阻塞的概念:
需要一直在这等着,不能去做其他的事情。

waitpid()

pid_ t waitpid(pid_t pid, int *status, int options);
返回值:
当正常返回的时候waitpid返回收集到的子进程的进程ID;
如果设置了选项WNOHANG,而调用中waitpid发现没有已退出的子进程可收集,则返回0;
如果调用中出错,则返回-1,这时errno会被设置成相应的值以指示错误所在;
参数:
pid:
Pid=-1,等待任一个子进程。与wait等效。
Pid>0.等待其进程ID与pid相等的子进程。
status:
WIFEXITED(status): 若为正常终止子进程返回的状态,则为真。(查看进程是否是正常退出)
WEXITSTATUS(status): 若WIFEXITED非零,提取子进程退出码。(查看进程的退出码)
options:
WNOHANG: 若pid指定的子进程没有结束,则waitpid()函数返回0,不予以等待。若正常结束,则返回该子进程的ID。

在这里插入图片描述

  1 #include<iostream>
  2 #include<stdlib.h>
  3 using namespace std;
  4 #include<unistd.h>
  5 #include<sys/wait.h>
  6 void mycallback()
  7 {
  8     cout<<"this is my callback"<<endl;
  9 }
 10 int main()
 11 
 12 {
 13     pid_t pid=fork();
 14 
 15     if(pid<-1)
 16     {
 17         cerr<<"create failed"<<endl;
 18     }
 19     else if(pid==0)
 20     {
 21         cout<<"i am son"<<endl;                                                                                                                                                       
 22         sleep(20);
 23     }
 24     cout<<"before receive"<<endl;
 25     int result=waitpid(pid,NULL,0);
 26     if(result<0)
 27     {
 28         cout<<"wait failed"<<endl;
 29     }
 30     else
 31     {
 32            cout<<"wait success"<<"pid is"<<pid <<endl;
 33     }
 34     return 0;
 35 }
~

在这里插入图片描述
为什么会打印waitfailed,这是因为子进程也在调用这个方法。对它来说,它没有要等的,所以在子进程中result是小于0的。
在这里插入图片描述
可以看到在options为0的情况下,其父进程就一直在等子进程,等到了就退出,并返回子进程的pid,0为阻塞模式。

根据进程退出码判断进程退出情况?

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

进程替换

在这里插入图片描述
在这里插入图片描述

替换函数

#include <unistd.h>`
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[]);

规律:带l的其参数列表以可变参数列表给出
带p的可以不用显式的给出其绝对路径
带v的起参数列表必须是一个指针数组
带e的表示程序员需要自己组织环境变量,不带e的可以不需要

execl

函数原型:
int execl(const charpath,const chararg,…);
在这里插入图片描述

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

代码运行结果:
在这里插入图片描述

execlp

在这里插入图片描述

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

在这里插入图片描述

exelce

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

execve

在这里插入图片描述

execv

在这里插入图片描述

    1 #include<unistd.h>
    2 #include<stdio.h>
    3 int main()
    4 {
    5     printf("process begin\\n");
    6     //execl("/usr/bin/ls","ls","-a","-l",NULL);
    7     //execlp("ls","ls","-a","-l",NULL);
    8     char *env[]={
    9     "ls",                                                                                                                                                                           
   10     "-a",
   11     "-l",
   12     NULL,};
   13 
   14    // execle("./mycom","./mycom",NULL,env);
   15    execv("/usr/bin/ls",env);
   16     printf("process end\\n");
   17     return 0;
   18 }

在这里插入图片描述

execvp

在这里插入图片描述

1 #include<unistd.h>
    2 #include<stdio.h>
    3 int main()
    4 {
    5     printf("process begin\\n");
    6     //execl("/usr/bin/ls","ls","-a","-l",NULL);
    7     //execlp("ls","ls","-a","-l",NULL);
    8     char *env[]={
    9     "ls",
   10     "-a",
   11     "-l",
   12     NULL,};
   13 
   14    // execle("./mycom","./mycom",NULL,env);
   15   // execv("/usr/bin/ls",env);
   16   execvp("ls",env);                                                                                                                                                                 
   17     printf("process end\\n");
   18     return 0;
   19 }
  ~

在这里插入图片描述

六个替换函数的关系

在这里插入图片描述
,execlp,execl,execle,execvp,execv它们五个为库函数,这五个库函数是封装了execve()系统调用函数的。

替换原理

用fork创建子进程后执行的是和父进程相同的程序(但有可能执行不同的代码分支),子进程往往要调用一种exec函数以执行另一个程序。当进程调用一种exec函数时,该进程的用户空间代码和数据完全被新程序替换,从新程序的启动
例程开始执行。调用exec并不创建新进程,所以调用exec前后该进程的id并未改变

进程替换的常见使用场景

1.bash命令行解释器。众所周知,bash通过创建子进程来执行程序,其本质原因是利用了进程独立性的特点,保护bash。
在这里插入图片描述
2。守护进程
在这里插入图片描述
好了,下课!!!

以上是关于Linux系统编程篇--进程控制篇的主要内容,如果未能解决你的问题,请参考以下文章

Linux 编程之进程篇:task_struct进程创建和退出

Linux 编程之进程篇:task_struct进程创建和退出

Linux 编程之进程篇:task_struct进程创建和退出

Linux下的编程实战

Linux系统编程--进程间通信 ---管道篇

linux网络编程之-----基础理论篇