文章目录
- 1 创建进程
- 2 进程终止
- 2.1 进程退出情况
- 2.2 进程终止的常见方式
- 2.2.1 return语句
- 2.2.2 exit()函数
- 2.2.3 _exit()函数
 
 
- 3进程等待
- 3.1 进程等待的重要性
- 3.2 进程等待的方法
- 3.2.1 wait()方法
- 3.2.2 waitpid()方法
 
 
- 4 进程替换
- 4.1 替换原理
- 4.2 替换函数
 
1 创建进程
fork()函数:从已经存在的进程中创建一个新进程,新进程为子进程,原来的进程为父进程。
进程调用fork函数之后,当控制转移到内核中的fork代码后,
内核做:
①分配新的内存块和内存数据结构给子进程。
②将父进程部分数据结构内容拷贝至子进程。
③添加子进程到系统进程列表当中。
④fork返回,开始调度器调度
fork之前父进程独立执行,fork之后,父子两个执行流分别执行。fork之后,谁先执行完全由调度器决定。
写时拷贝
通常,父子代码共享,父子不再写入时,数据也是共享的,当任意一方试图写入数据,便以写时拷贝的方式各自一份副本。具体见下图

写时拷贝的好处:使父子进程得以彻底分离,可以实现进程独立性
写时拷贝,是一种延时申请技术,可以提高整机内存的使用率
2 进程终止
2.1 进程退出情况
进程退出情况
代码运行完毕,结果正确
代码运行完毕,结果不正确
代码异常终止
进程运行正确,返回值为0,代表success;
进程运行错误,返回非0值作为错误码,一个错误码对应着一个错误信息
退出码只能确定程序结果运行是否正确,当程序异常退出时,错误码就没有任何意义了。

 自己可以使用这些退出码和含义,当然,也可以自己设计出一套退出方案
2.2 进程终止的常见方式
2.2.1 return语句
只有main函数的return语句才代表进程终止,其它函数的return语句只代表函数结束。
由此可知:main函数的返回值不一定要是0,而是要根据自己的实际情况选择不同的返回值
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
int main()
{
int number=0;
int sum=0;
for(number=1;number<=100;number++)
{
sum+=number;
}
if(sum==5050)
{
  return 0;//结果正确
}
else{
  return 1;//结果错误
}
}

#include<stdio.h>
#include<stdlib.h>
#include<string.h>
int main()
{
int number=0;
int sum=0;
for(number=1;number<=100;number++)
{
sum+=number;
}
sum=0;//将结果改为错误的
if(sum==5050)
{
  return 0;//结果正确
}
else{
  return 1;//结果错误
}

2.2.2 exit()函数
#include<stdio.h>
#include<stdlib.h>
int main()
{
  printf("hello world\n");
  sleep(3);
  exit(0);
}
exit()放在任意位置(主函数或者其他函数)都可以结束进程
参数是退出码,可以是任意整数
exit()在结束进程的同时会刷新缓冲区
#include<stdio.h>
#include<stdlib.h>
int main()
{
  printf("hello world");
  sleep(3);
  exit(22);
}
printf输出打印的时候没有加\n,数据会先在缓冲区,休眠3秒以后,exit()函数会刷新缓冲区,打印出语句

2.2.3 _exit()函数
和exit()唯一的区别就是_exit()只会终止进程,不会刷新缓冲区
#include<stdio.h>
#include<stdlib.h>
int main()
{
  printf("hello world");
  sleep(3);
  _exit(22);
}

 可以看到没有打印出语句
 
3进程等待
3.1 进程等待的重要性
子进程退出,如果父进程什么都不管,就会造成僵尸进程问题,进而会产生内存泄露
另外,进程一旦变成僵尸状态,即使发送kill -9信号,也无法杀掉僵尸进程
所以父进程通过进程等待的方式,回收子进程资源,获取子进程的退出信息,进而也可以知道父进程派给子进程的任务完成如何
3.2 进程等待的方法
3.2.1 wait()方法
等待成功,返回所终止子进程的pid;等待失败返回-1
#include<stdio.h>
#include <sys/types.h>
#include <sys/wait.h>
#include<unistd.h>
#include<stdlib.h>
int main()
{
  int cnt=5;
  pid_t id=fork();
  if(id<0)
  {
     perror("fork");
     exit(1);
  }
  else if(id==0)
  {
    while(cnt--)
    {
      printf("I am child\n");
     sleep(1);
    }
    
  }
  else{
    sleep(10);
    printf("I am father\n");
    pid_t ret=wait(NULL);
    if(ret>0)
    {
        printf("等待子进程成功 ,子进程pid:%d\n",ret);
       sleep(3); 
    }
    else{
      printf("等待子进程失败\n");
    }
  }
}

 
3.2.2 waitpid()方法
pid_t waitpid(pid_t pid, int *status, int options)
| 组成 | 说明 | 
|---|---|
| 返回值 | ①等待成功&&子进程退出:返回子进程的pid ②等待失败:返回-1 | 
| 参数pid | ①设置为指定进程的pid,代表等待某个指定的进程,②若设置为-1,代表等待任意的进程 | 
| 参数status | 是输出型参数,获得进程退出的结果 | 
| 参数option | option为等待方式,①设置为0代表默认行为,即阻塞等待 ②设置为WNOHANG,代表非阻塞等待 | 
参数status
wait和waitpid,都有一个status参数,该参数是一个输出型参数,由操作系统填充
如果传递为NULL,表示不关心子进程的退出状态信息
否则,操作系统会根据该参数,将子进程的退出信息反馈给父进程
status不能简单的当做整形来看待,可以当做位图来看待

下标[0,7)的7个比特位代表终止信号,如果收到了信号,说明进程是异常终止,此时便不需要关注退出码
下标[8,16)这8个比特位代表退出码,如果是正常退出,此时信号为0,需要关注退出码
下标为7的比特位代表core dump标志
正常退出的情况
#include<stdio.h>
#include <sys/types.h>
#include <sys/wait.h>
#include<unistd.h>
#include<stdlib.h>
int main()
{
  int cnt=5;
  pid_t id=fork();
  if(id<0)
  {
     perror("fork");
     exit(1);
  }
  else if(id==0)
  {
    while(cnt--)
    {
      printf("I am child,pid:%d,ppid:%d\n",getpid(),getppid());
     sleep(1);
    }
    exit(20);
  }
  else{
    sleep(10);
    printf("I am father\n");
   // pid_t ret=wait(NULL);
    int status=0;
    pid_t ret=waitpid(id,&status,0);//父进程的id就是子进程的pid
    if(ret>0)
    {
        printf("等待子进程成功 ,子进程pid:%d,子进程收到的信号编号:%d,子进程退出码:%d\n",ret,status&0x7F,(status>>8)&0xFF);
       sleep(3); 
    }
    else{
      printf("等待子进程失败\n");
    }
  }
}

 异常退出的情况
#include<stdio.h>
#include <sys/types.h>
#include <sys/wait.h>
#include<unistd.h>
#include<stdlib.h>
int main()
{
  int cnt=5;
  pid_t id=fork();
  if(id<0)
  {
     perror("fork");
     exit(1);
  }
  else if(id==0)
  {
    while(cnt--)
    {
      printf("I am child,pid:%d,ppid:%d\n",getpid(),getppid());
     sleep(1);
    }
    int *p=NULL;
    *p=100;//野指针
    exit(20);
  }
  else{
    sleep(10);
    printf("I am father\n");
   // pid_t ret=wait(NULL);
    int status=0;
    pid_t ret=waitpid(id,&status,0);//父进程的id就是子进程的pid
    if(ret>0)
    {
        printf("等待子进程成功 ,子进程pid:%d,子进程收到的信号编号:%d,子进程退出码:%d\n",ret,status&0x7F,(status>>8)&0xFF);
       sleep(3); 
    }
    else{
      printf("等待子进程失败\n");
    }
  }
}


 可以看到 11号信号为SIGSEGV
SIG是信号名的通用前缀
SEGV为segmentation violation 即存储器区段错误,无效的内存引用
参数option
阻塞等待:在子进程执行任务期间,父进程一直处于阻塞队列,直到子进程退出
非阻塞等待:不断的轮询检测子进程的状态,若子进程没有退出,父进程可以执行自己的代码
此时返回值就分为三种情况
等待成功&&子进程退出:返回子进程的pid
等待成功&&子进程未退出:返回0(非阻塞等待)
等待失败:返回-1
非阻塞等待案例
#include<iostream>
#include<vector>
#include<stdio.h>
#include <sys/wait.h>
#include<unistd.h>
#include<stdlib.h>
typedef void (*handler_t)();//函数指针类型
    std::vector<handler_t>handlers;
void fun_one()
{
  printf("这是一个临时任务1\n");
}
void fun_two()
{
  printf("这是一个临时任务2\n");
}
void Load()//父进程闲了想要执行任何方法的时候,只要向Load里面注册,就可以让父进程执行对应的方法
{
handlers.push_back(fun_one);
handlers.push_back(fun_two);
}
int main()
{
  int cnt=5;
  pid_t id=fork();
  if(id<0)
  {
     perror("fork");
     exit(1);
  }
  else if(id==0)
  {
    while(cnt)
    {
      printf("我是子进程:%d\n",cnt--);
     sleep(1);
    }
    
    exit(20);
  }
  else{
         int quit=0;
         while(!quit)
         {
           int status=0;
           pid_t res=waitpid(-1,&status,WNOHANG); //以非阻塞方式等待
           if(res>0)
           {
             //等待成功&&子进程退出
             printf("等待子进程成功,退出码:%d\n",WEXITSTATUS(status));
             quit=1;
           }
           else if(res==0)
           {
             //等待成功&&子进程未退出
             printf("子进程还在运行中,父进程可以等一等\n");
              if(handlers.empty())
              {
                Load();
              }
                for(auto iter:handlers)
                {
                
                  iter();
                 }
            }
           else{
             printf("wait失败\n");
             quit=1;
           }
           sleep(1);
         }
  }
}
对于获取子进程退出结果的参数status,除了使用位操作,还可以使用系统定义好的宏
WIFEXITED(status): 若为正常终止子进程返回的状态,则为真。(查看进程是否是正常退出)
WEXITSTATUS(status): 若WIFEXITED非零,提取子进程退出码。(查看进程的退出码)

4 进程替换
4.1 替换原理
用fork创建子进程后执行的是和父进程相同的程序(但有可能执行不同的代码分支),子进程往往要调用一种exec函数,以执行另一个程序。当进程调用一种exec函数时,该进程的用户空间代码和数据完全被新程序替换,从新程序的启动例程开始执行。调用exec并不创建新进程,所以调用exec前后该进程的id并未改变。
简单来说:用exec()函数进行程序替换,就是将新的磁盘上的程序加载到内存,并和当前进程的页表,重新建立映射
4.2 替换函数
①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[]);
函数解释
这些函数如果调用成功则加载新的程序,从启动代码开始执行,不再返回。
如果调用出错则返回-1。
所以exec函数只有出错的返回值而没有成功的返回值。
命名理解
①l(list) : 表示参数采用列表
②v(vector) : 参数用数组
③p(path) : 有p自动搜索环境变量PATH
④e(env) : 表示自己维护环境变量
常用的exec函数举例
#include<stdio.h>
#include<unistd.h>
#include<sys/wait.h>
#include<stdlib.h>
# define NUM 16
const char* myfile="./mycmd";
int main()
{
  pid_t id=fork();
  if(id==0)
  {
   // char* _argv[NUM]={"ls","-a","-l",NULL};
   char* _argv[NUM]={"mycmd","-a",NULL};
    printf("子进程开始运行,子进程pid:%d\n",getpid());
    sleep(3);
//    execl("/usr/bin/ls","ls","-a","-l",NULL);//参数使用列表的方式执行ls -a -l命令
//     execv("/usr/bin/ls",_argv);//参数使用数组的方式执行ls -a -l命令
   //  execlp(myfile,"mycmd","-b",NULL);//自动在环境变量中找命令
  //     execlp(myfile,"mycmd","-a",NULL);
//      execvp(myfile,_argv);
      execlp("java","java","hello",NULL);//在自己的程序里面又去调用java程序
    exit(1);
  }
  else{
    printf("父进程开始运行,父进程pid:%d\n",getpid());
    int status=0;
    pid_t res=waitpid(-1,&status,0);//阻塞等待
    if(res>0)
    {
      printf("wait success,exit code:%d\n",WEXITSTATUS(status));
    }
  }
  return 0;
}
#include<stdio.h>
#include<string.h>
#include<stdlib.h>
int main(int argc,char* argv[])//mycmd -a或mycmd -b
{
  if(argc!=2)
  {
    printf("can not execute\n");
    exit(1);
  }
  if(strcmp(argv[1],"-a")==0)
  {
    printf("hello a!\n");
  }
  else if(strcmp(argv[1],"-b")==0)
  {
    printf("hello b!\n");
  }
  else{
    printf("default!\n");
  }
  return 0;
}



![[问题解决] ubuntu 18.04 GPU驱动安装](https://img-blog.csdnimg.cn/8f9e63c576104e0d80f7a6eaf4eadce8.png)
















