Linux系统:进程控制

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是输出型参数,获得进程退出的结果
参数optionoption为等待方式,①设置为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;
}

在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值