Linux系统编程基础:进程控制

文章目录

进程控制主要分为三个方面,分别是:子进程的创建,进程等待,进程替换

一.子进程的创建

  • 父进程调用fork()系统接口创建子进程后,操作系统会为子进程创建独立的PCB结构体和虚拟地址空间mm_struct,因此父子进程之间具有互相独立性

操作系统内核视角下的父子进程存在形式

  • 父进程调用fork()函数之后:

验证子进程对父进程数据的写时拷贝

cpp 复制代码
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
int g_val = 0;
int main(int argc,const char * argv[])
{
	//创建子进程
    pid_t id = fork();
    if(id < 0)
    {
      //创建子进程失败,退出主函数
      perror("fork");
      return 0;
    }
    else if(id == 0)  
    {
      //子进程执行流
      //子进程对变量g_val进行修改,引发写时拷贝
      g_val=100;
      printf("childProcess[%d]: %d : %p\n", getpid(), g_val, &g_val);
    }
    else
    {
       //父进程的执行流
       //父进程先休眠3秒,让子进程先完成g_val变量的写入
       sleep(3);
       printf("parentProcess[%d]: %d : %p\n", getpid(), g_val, &g_val);
    }
    sleep(1);
    return 0;
}
  • 执行结果:
  • 内核视角下,代码段中的写时拷贝图解:
  • 写时拷贝保证了父子进程之间互相独立的同时提高了计算机整机的内存使用效率

二.进程等待

  • 子进程退出后会进入僵尸状态,僵尸状态进程的内核数据结构对象会保留在操作系统内核空间中,在父进程读取子进程的退出信息后,操作系统才会释放掉子进程所占用的所有系统资源
    • 若父进程比子进程先退出,那么操作系统就会接管子进程(成为孤儿进程),子进程退出后,操作系统会自动读取子进程的退出信息.
  • 父进程读取子进程的退出信息这一过程称为进程等待
  • 进程等待常用系统接口:int waitpid(int pid, int *status, int options);
    • 形参int pid:

    • 形参int *status:用于记录进程退出码和退出信号的输出型参数

    • 形参int options表示进程等待的方式:主要分为阻塞等待和非阻塞等待两大类:

      • option0时:进程执行阻塞等待,此时进程会进入阻塞状态(PCB退出运行队列,进入阻塞队列),直到其所等待的子进程退出,该进程才会重新进入运行状态读取子进程的退出信息
      • option为非零时(比如使用系统宏WNOHANG):进程执行非阻塞等待, 此时进程会尝试读取其子进程的退出信息,若没能读取到子进程的退出信息,则waitpid系统接口立即返回0
    • 返回值int:若waitpid系统接口读取到子进程的退出信息,返回子进程的pid,若waitpid执行过程中出现错误,则返回-1

进程非阻塞等待示例:

cpp 复制代码
#include <unistd.h>
#include <stdio.h>
#include <sys/wait.h>
#include <stdlib.h>

int main()
{
  	  int pid = 0;
  	  //创建子进程
	  pid = fork();
	  if(pid == -1)
	  {
		    //子进程创建失败
		    printf("fork failed\n");
		    return -1;
	  }
	  else if(pid == 0)
	  {
		    //子进程的代码执行流
		    int cnt = 5;
		    while(cnt--)
		    {
		        sleep(1);
		        printf("child process running\n");
		    }
		    exit(11);
	  }
	  else
	  {
		   //父进程的代码执行流
		   //非阻塞等待子进程
		   int status = 0;
		   bool quit = 0;
		   while(quit == 0)
	   	   {
		      //循环非阻塞等待子进程
		      int res =  waitpid(pid,&status,WNOHANG);
		      if(res > 0)
		      {
		      	 //子进程已退出
		         printf("waiting pid success,chilProc exit,退出码:%d\n",WEXITSTATUS(status));
		         quit = 1;
		      }
		      else if(res == 0)
		      {
		         //子进程还未退出
		         printf("waiting pid success,childProc still running\n");
		      }
		      else
		      {
		         //等待发生错误
		         printf("waiting pid failed\n");
		      }
		      sleep(1);
		   }
	  }
	  return 0;
}

三.进程替换

  • 进程替换的概念:通过特定的系统调用接口,操作系统可以将进程当前执行的代码段替换成指定系统路径下其他可执行程序的代码段,然后根据进程从头开始执行新的代码段(因此需要通过进程替换系统接口为新代码段传入main函数命令行参数)

内核视角下的进程替换过程:


  • 可见进程替换并不是创建新的进程(进程的PCB和虚拟内存结构体不变)
  • 注意:进程替换过程中,一些全局性的数据(比如环境变量)不会被替换掉
  • 进程替换系统接口:
  • 形参和返回值统一解释:
    • 参数const char*path:表示将要替换现有代码段的目标可执行程序的完整路径
    • 参数const char*file:表示将要替换现有代码段的目标可执行程序的文件名,其系统路径由环境变量PATH决定
    • 参数const char*arg,...:表示传给新代码段main函数命令行参数的字符串,...表示可变参数列表,可以传入多个字符串,最后一个字符串需传入NULL
    • 参数char *const argv[]:表示传给新代码段main函数命令行参数的字符串数组,数组中最后一个字符串需传入NULL
    • 参数cahr*const envp[]:表示传递给新代码段的环境变量字符串数组
    • 当进程替换失败时,exec系列系统接口会返回-1
  • 实质上,exec*系列系统接口在Linux系统中充当着指令集和数据加载器的角色

综合利用进程控制系统接口实现简单的shell进程

  • shell进程的运行原理:shell进程接收到用户输入的指令后,对指令进行格式化处理,然后创建子进程,子进程通过exec系列系统接口将自身替换成系统命令并执行以响应用户需求,shell进程的这种运行机制保证了自身的进程安全(子进程出现错误不会影响到父进程的运行)
cpp 复制代码
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/wait.h>
#include <string.h>
#include <memory.h>

//用户输入的命令行的最大长度
#define CStrLen 1024
//解析命令行得到的格式化字符串数组
#define SStrLen 50
//命令行字符串
char CommStr[CStrLen];
//格式化命令行字符串数组
char*  StdStr[SStrLen];

//命令行分隔符
#define Sep " "
//操作系统配置的环境变量
extern char ** environ;
int main()
{
  while(1) 
  {
     printf("[我的命令行解释器 myshell]$ ");
     fflush(stdout);
     memset(CommStr,'\0',sizeof StdStr);
     //获取用户输入命令
     if(fgets(CommStr,sizeof CommStr,stdin)== NULL)
     {
       continue;
     }
     CommStr[strlen(CommStr)-1] = '\0';
     StdStr[0] = strtok(CommStr,Sep);
     //根据空格对用户输入的字符串进行分割并存入StdStr字符串数组中
     int i = 1;
     while(StdStr[i++] = strtok(NULL,Sep));
  
     //部分命令(比如cd命令)需要由shell进程自己来执行
     if(strcmp(StdStr[0],"cd")== 0)
     {
        //用chdir函数改变shell进程的工作路径
        if(StdStr[1] != NULL)
        {
            chdir(StdStr[1]);
        }
        continue;
     }


     //shell创建子进程来执行系统命令
     int pid = fork();
     if(pid == 0)
     {
        //printf("shell的子进程执行系统命令:\n");
        execvp(StdStr[0],StdStr);
        printf("-mybash: %s: command execute failed\n",StdStr[0]);
        exit(-1);
     }
     else
     {
        int status;
        //父进程进行阻塞等待,等待子进程执行完系统命令结束并获取其退出码
        int waitres = waitpid(pid,&status,0);
        if(waitres == -1)
        {
           printf("waitchild process failed\n");
        }
     }
  }
  return 0;
}


相关推荐
hakesashou19 分钟前
python如何比较字符串
linux·开发语言·python
Ljubim.te27 分钟前
Linux基于CentOS学习【进程状态】【进程优先级】【调度与切换】【进程挂起】【进程饥饿】
linux·学习·centos
cooldream200940 分钟前
Linux性能调优技巧
linux
QMCY_jason1 小时前
Ubuntu 安装RUST
linux·ubuntu·rust
慕雪华年1 小时前
【WSL】wsl中ubuntu无法通过useradd添加用户
linux·ubuntu·elasticsearch
苦逼IT运维2 小时前
YUM 源与 APT 源的详解及使用指南
linux·运维·ubuntu·centos·devops
仍有未知等待探索2 小时前
Linux 传输层UDP
linux·运维·udp
zeruns8022 小时前
如何搭建自己的域名邮箱服务器?Poste.io邮箱服务器搭建教程,Linux+Docker搭建邮件服务器的教程
linux·运维·服务器·docker·网站
卑微求AC2 小时前
(C语言贪吃蛇)16.贪吃蛇食物位置随机(完结撒花)
linux·c语言·开发语言·嵌入式·c语言贪吃蛇
Hugo_McQueen2 小时前
pWnos1.0 靶机渗透 (Perl CGI 的反弹 shell 利用)
linux·服务器·网络安全