当前位置: 首页 > article >正文

进程的控制(创建、终止、等待,程序替换)

一、进程的创建

进程=内核的相关管理数据结构(task_struct + mm_struct+页表) + 代码和数据。

进程创建时,先创建内核数据结构,再将代码和数据加载到内存中。

如何理解进程的独立性:即使是父子进程,其中代码是共享的,数据在进行修改时发生写时拷贝;并且子进程有自己的内核管理数据结构。更不要说两个不相关的进程了。

除了“./程序名”的方式之外(该方式是让bash进程自己创建子进程)可以自己使用fork()函数创建。

fork函数返回值:

子进程返回0;父进程返回子进程的pid。原因:让父进程返回子进程的id是为了方便让父进程对子进程进行标识,进而进行管理;给子进程返回0是表示子进程创建成功。


二、进程的终止

1、终止是在做什么

做的工作分两项:(1)释放曾经的代码和数据所占的空间;(2)释放内核数据结构。

:在做第二项工作的时候,先讲mm_struct和页表等释放,而task_struct需要延时处理,因为 task_struct中存放了进程退出的信息。也就是说,进程终止时,如果父进程不来读取该子进程的退出信息,那么子进程则会处于Z状态。

2、进程终止的3种情况

(1)代码跑完,结果正确;

(2)代码跑完,结果不正确;

(3)代码执行时,出现异常,提前退出。

实验证明:

实验一:进程正常退出

注:“?”表示的是父进程bash获取到的最近一个子进程退出的退出码。(但是实际上,内建命令由父进程bash本身完成,也会对“?”的变量值做修改,实验如下:)

退出码的意义是告诉父进程,把任务完成的咋样了。一般退出码为0,则表示进程跑完结果正确;而非0,则表示跑完结果失败,不同的非0数字表示了不同的失败原因,也就是错误描述。

退出码可以默认,也可以自定义。

另一个默认错误:

实验二:进程异常退出

上面这个实验表示的是死循环的进程被 kill -9信号杀死,killed表示进程异常退出的信号。

本质:进程收到了OS发给进程的信号。

衡量一个进程退出,只需要两个数字:退出码,退出信号。

退出信号非0,就不关心退出码了;只有退出信号为0(正常退出)时,退出码才有意义。

3、如何终止

(1)main函数的return,表示进程终止(非main函数的return表示函数结束)return(n)等价于exit(n)

(2)exit函数——C库的一个函数,头文件需包含 stdlib.h

(3)_exit()——系统调用方式(system call)

注:exit函数和_exit()方式的区别:exit函数是C库的一个函数,底层肯定是要调用_exit()的,否则无法调用OS的功能。但下列实验说明二者是有区别的:

利用exit(0)退出:

利用_exit(0)退出:

也就是说,我们这里的缓冲区不在内核数据结构中,否则两种方式应该都会打印才对。

实际上exit和_exit的区别在于:

三、进程等待

1、进程等待的概念

任何子进程,在退出的情况下,一般必须要被父进程进行等待。进程在退出时,如果父进程不去接收子进程的退出信息,那么子进程就会处于僵尸状态,一直处于僵尸状态则会导致内存泄漏的问题。

2、进程等待的意义

(1)父进程通过等待,解决子进程退出的僵尸问题,回收系统资源(这是必须要做的)

(2)获取子进程的退出信息,知道子进程是因为什么原因退出的(可以选择的功能)

3、如何实现进程等待

(1)wait()函数

使用前需包含:#include<sys/types.h>   #include<sys/wait.h>

wait(NULL)或者wait(&status)本质是等待任一子进程。

(2)waitpid()函数

pid_ t  waitpid(pid_t pid, int *status, int options)

也就是说,waitpid有三个参数,第一个参数表示的是等待的子进程的pid,第二个为输出型参数,用于获取子进程的退出信息。第三个参数为0表示阻塞等待。

特殊地,pid参数为1,那么等同于wait函数的作用。

status说明:

这里status是一个输出型参数,表达的含义是子进程退出的信息(退出码+退出信号),这里用位图的形式表示。int 是一个32位整数,但这里只用到了它的后16位。

后16位的前8位表示的是子进程的退出码,最后的7位表示的是进程的退出信号,中间一位特殊位。要想看到具体的退出码和退出信号,进行运算:

不运算,有现成的两个宏可以获取子进程是否正常退出以及查看进程的退出码:

WIFEXITED(status):若为正常终止子进程返回的状态,则为真。(查看进程是否是正常退出);

WEXITSTATUS(status):若WIFEXITED非零,提取子进程退出码。(查看进程的退出码)。

实验:

将野指针错误去掉的运行结果:

阻塞等待的实质:

将进程的task_struct链接到子进程上而不是在调度队列中,直到子进程退出。

非阻塞等待

当将options参数设置成“WNOHANG”时,实现非阻塞等待。返回值的情况如下:

返回的整数pid_t >0:等待成功,子进程退出,并且父进程回收成功;

返回的整数pid_t ==0:等待成功,但子进程还没退出,需要进行下一次重复等待;

返回的整数pid_t <0:等待失败。(可能是由于指定的子进程id错误或其他原因)。

使用非阻塞状态的时候,需要和循环一起使用,构成非阻塞轮询。循环的意义就是不停地去查看子进程是否退出(返回的pid_t是否为0)。非阻塞状态的意义就是允许父进程做一些其他的事情。

实验:


void ChildRun()
{
  int cnt=5;
  while(cnt)
  {
      printf("I am child process,pid=%d,ppid=%d,cnt=%d\n",getpid(),getppid(),cnt);
      sleep(1);
      cnt--;

  }
//  int *p=NULL;
//  *p=1;
}

void loadtask()
{
  printf("I am going to load some tasks\n");
}

void Dosometask()
{
  printf("I am father process,I am here to do something!\n");

}
int main()
{
    printf("I am father process,pid:%d\n",getpid());
    int  id =fork();

    if(id==0)
    {
        //子进程
        ChildRun();
        printf("child exit...\n");
        exit(0);
    }
    int status=0;
    loadtask();
    while(1)
    {
       
        sleep(1);
        int childpid= waitpid(id,&status,WNOHANG);//非阻塞等待实验
        if(childpid>0)
        {
            
            if(WIFEXITED(status))
            {

                printf("child pid:%d, exit_code:%d\n",childpid,WEXITSTATUS(status));
            }
            else{
              printf("child faild exit!\n");
            }
            break;
        }
        else if(childpid==0)
        {
          
          //让父进程做一些事情
          Dosometask();
        }
        else{
          //等待失败
          printf("wait failed!\n");
          break;//一般不会出现这种情况
        }
    }
  //  printf("child pid:%d, exit_code:%d,exit_signal:%d\n",childpid,(status>>8)&0xff,status&0x7f);
    printf("father exit...\n");
    exit(0);
}

四、进程的程序替换

1、替换原理

替换的本质是将要执行的程序的数据与代码替换到本来执行的程序数据与代码中,也就是将磁盘中的要执行的代码和数据换到内存中。程序替换所调用的函数是exec*系列函数。程序替换并不会创建新进程,所以调用exec*系列函数前后该进程的id并未改变。

一般父进程创建子进程,用子进程去替换一些程序,原理示意图如下:

站在磁盘中被替换的程序而言,是这个程序被加载到内存中了,也就是说,exec*系列函数类似于一种Linux中的加载函数,要么这个函数本身是系统调用的接口,要么其底层使用了系统调用。

2、实验&exec系列函数的使用

(1)对系统命令的替换实验

#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<sys/types.h>
#include<sys/wait.h>


int main()
{
    //exec系列函数的使用
    int id=fork();
    extern char** environ;
    if(id==0)
    {
      //child process
      //execl("/usr/bin/ls","ls","-a","-l",NULL); //l表示的是list的意思
      

       char* const  argv[]=
      {
          (char*)"ls",
          (char*)"-a",
         (char*) "-l",//强转一下防止警告
          NULL
      };
     // execv("/usr/bin/ls",argv);//v表示的是vector的意思
     

      //execvp("ls",argv);//带p会使用环境变量进行自动查找,无需写全路径
      

   //    extern char** envrion;
       execvpe("ls",argv,environ);//e表示环境变量 可以用全新的,也可以使用老的,也可对老的进行修改给子进程


       exit(1);//替换失败才会到这里

    }
    int status;
    int childid=waitpid(id,&status,0);
    if(WIFEXITED(status))
    {
      printf("childprocess id:%d,exitcode:%d\n",childid,WEXITSTATUS(status));
    }
    sleep(1);
    printf("father process exit\n");
    
    
    
    
    
    
    return 0;
}

(2)对自己写的程序的替换实验

主程序:

#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<sys/types.h>
#include<sys/wait.h>


int main()
{
    //exec系列函数的使用
    int id=fork();
    extern char** environ;
    if(id==0)
    {
      //child process
      //execl("/usr/bin/ls","ls","-a","-l",NULL); //l表示的是list的意思
      

      // char* const  argv[]=
      //{
      //    (char*)"ls",
      //    (char*)"-a",
      //   (char*) "-l",//强转一下防止警告
      //    NULL
      //};
     // execv("/usr/bin/ls",argv);//v表示的是vector的意思
       char* const  argv[]=
      {
          (char*)"mytest",//已找到位置不用加./
         // (char*)"-a",
        // (char*) "-l",//强转一下防止警告
          NULL
      };
     

      //execvp("ls",argv);//带p会使用环境变量进行自动查找,无需写全路径
      

   //    extern char** envrion;
       execvpe("./mytest",argv,environ);//e表示环境变量 可以用全新的,也可以使用老的,也可对老的进行修改给子进程
      //execl("./mytest","mytest",NULL);

       exit(1);//替换失败才会到这里

    }
    int status;
    int childid=waitpid(id,&status,0);
    if(WIFEXITED(status))
    {
      printf("childprocess id:%d,exitcode:%d\n",childid,WEXITSTATUS(status));
    }
    sleep(1);
    printf("father process exit\n");
    
    
    
    
    
    
    return 0;
}

被替换的程序:

#include <iostream>
#include<unistd.h>

using namespace std;

int main(int argc,char* argv[],char* env[])
{
    int i =0;
    for(;argv[i];i++)
    {
        cout<<argv[i]<<endl;
    }
    cout<<"-------------------"<<endl;
    for(i=0;env[i];i++)
    {
      cout<<env[i]<<endl;
    }
    cout<<"-------------------"<<endl;
    cout<<"this is child process"<<endl;
    cout<<getpid()<<endl;
    return 0;
}

makefile文件(一次编译多个文件)

.PHONY: all
all:testexec mytest

testexec:testexec.c
	gcc -std=c99  -o  $@ $^

mytest:mytestcc.cpp
	g++  -o $@ $^

.PHONY:clean
	clean:rm-f testexec mytest

注意,这里程序替换是对进程的替换,这意味着不仅能替换c、c++编译得到的程序,对其他程序例如python shell等脚本语言执行所得到的进程也可以进行替换。

在这些函数中,只有execve是系统调用,而其他函数底层是调用execve的。


http://www.kler.cn/news/366107.html

相关文章:

  • 2024Flutter面试题
  • STM32Lx GXHT3x SHT3x iic 驱动开发应用详解
  • 案例分析-嵌入式系统
  • 反向代理服务器---NGINX
  • centos服务器重启后,jar包自启动
  • NewStarCTF 2023 公开赛道 Web week1-week2
  • 从0到1,搭建vue3项目
  • Midjourney计划推出一款升级版的网页工具
  • 接口自动化-Yaml文件引用CSV
  • Android View的事件分发机制
  • Java的SKU探秘之旅:API数据的极速捕获
  • 第71期 | GPTSecurity周报
  • 爬虫日常实战
  • 持续深化信创布局,途普科技与统信软件完成产品兼容性互认证
  • 《编程并不难:像学语文一样学习编程语言》
  • Vue 的 Diff 算法解析
  • 【Golang】Golang的GC垃圾回收机制
  • 电脑异常情况总结
  • 论文笔记(五十)Segmentation-driven 6D Object Pose Estimation
  • 什么是甘特图?
  • Linux 学习笔记(十七)—— 文件系统
  • 从0到1构建 UniApp + Vue3 + TypeScript 移动端跨平台开源脚手架
  • word表格跨页后自动生成的顶部横线【去除方法】
  • 三个路由练习
  • 将 el-date-picker获取的时间数据转换成时间戳
  • 小程序开发实战:PDF转换为图片工具开发