Linux 之 进程控制(~~~内容通俗易懂)


前言

进程控制,实则就是对进程进行的创建,终止,等待,替换操作,本文内容也将围绕这几个点进行哟!


1、进程创建

那么该如何创建一个进程呢?!?

pid_t fork(void);   写时拷贝技术的方式创建一个子进程,父子进程(代码共享,数据独有)
pid_t vfork(void);  创建一个新的子进程,父子进程共用同一块虚拟地址空间

1.1、fork(void)函数

fork()函数,以及其中的写时拷贝技术,在上文中有详细介绍,有需要的铁铁的们阔以瞅瞅~~~
博客连接-------进程概念(包含fork函数详细讲解)

1.2、vfork(void)函数

因为vfork函数,其创建子进程之后会与父进程共用同一块虚拟地址空间~
因此其用的是同一个栈,故一个进程对数据进行改变,也会在另一方体现(这其中就存在了调用栈混乱的隐患!!!
在这里插入图片描述

  • 面对上述隐患,故而vfork创建子进程后,父进程需阻塞,知道子进程exit退出或者进行了进程替换,创建了自己的地址空间,以及各项数据

2、进程终止、

2.1、概念及场景

终止: 即进程退出运行
退出场景: 正常(符合预期&不符合预期) 异常

2.2、退出方式

  • 1.main函数中return退出;(注:return只有在main中才是退出运行
  1 #include <stdio.h>
  2 #include <unistd.h>
  3 #include <stdlib.h>
  4 int func()
  5 {
  6   printf("func()\n");
  7   return 0;
  8 }
  9 int main()
 10 {
 11   func();
 12   printf("hello world\n");
 13   return 0;
 14 } 

在这里插入图片描述
hello world之所以被打印出来,是由于func函数中的return并未退出进程!!

  • 2.库函数: void exit(int return_val); // 可以在程序任意位置进行调用退出进程
  • 3.系统调用接口:void _exit(int return_val);//可以在程序任意位置进行调用退出进程
  1 #include <stdio.h>
  2 #include <unistd.h>
  3 #include <stdlib.h>
  4 int func()
  5 {
  6   printf("func()\n");
  7   exit(0);
  8  // _exit(0);
  9 }
 10 int main()
 11 {
 12   func();
 13   printf("hello world\n");
 14   return 0;
 15 }

在这里插入图片描述
较刚刚那段代码,也体现了exit和_exit函数可于任何位置退出进程
但是,exit()这个库函数是_exit这一系统调用接口的封装,区别在哪里呢???

  1 #include <stdio.h>
  2 #include <unistd.h>
  3 #include <stdlib.h>
  4 void func()
  5 {
  6   printf("func()");//无\n刷新缓冲区
  7   exit(0);
  8 }
  9 void func1()
 10 {
 11   printf("不存在‘\n’的func1()");//无\n刷新缓冲区
 12   _exit(0);
 13 }
 14 int main()
 15 {
 16   func();
 17   func1();                                                                                                                                                          
 18   return 0;
 19 }

在这里插入图片描述

  • 其中只有func()函数打印结果输出!!!

区别:
1. 库函数,退出前刷新缓冲区中的数据;
2. 系统调用接口,直接退出释放资源

  • buff-缓冲区:相对于文件来说,就是数据写入文件前,先放到缓冲区,积累成大数据后,一次性刷新缓冲区,写入文件中,减少IO次数
  • cache-缓存:相对于文件中读取数据,一次拿出的是一个磁盘块的磁盘数据放到内存中,下次读取数据先从缓存中对比寻找

3、进程等待

作用:等待子进程退出,获取子进程退出返回值,释放子进程资源,防止其成为僵尸进程

3.1、进程等待的方法

3.1.1、wait方法

  pid_t wait(int* status);
参数:status,输出形参数,用于获取子进程退出的返回值
                             (若不关心其可设置为NULL)
返回值: 成功,返回子进程的pid,大于0
		失败,返回-1

wait接口是一种阻塞(可中断休眠)等待,等待任意子进程退出,若无子进程退出,则阻塞,一直等待

 #include <stdio.h>
 #include <stdlib.h>
 #include <sys/uio.h>
 #include <unistd.h>   
 #include <sys/wait.h>
  int main()
 {
   pid_t ret =fork();
   if(ret<0)
   {
     perror("fork error\n");
     return -1;
   }
   else if(ret==0) 
   {
    sleep(3);
    exit(66);
   }
   int exit_code;
    int res=wait(&exit_code);//阻塞等待任意一个子进程退出
   	if(res<0)
   	{
   		perror(“wait error”);
   		return -1;
   	}
   	printf("%d\n",exit_code);
}

在这里插入图片描述

3.1.2、waitpid方法

 pid_t waitpid(pid_t pid,int* status,int options);
 参数:pid,制定等待子进程的ID,-1表示等待任意进程退出
 	  status,同wait函数中status参数
 	  options,操作选项:
 	     0   :表示阻塞等待,
 	  WNOHANG:非阻塞等待,若无子进程退出(包括子进程已经退出),不阻塞
 	  ——需循环使用,否则可能参数僵尸进程
返回值:
      正常:返回子进程pid,大于0
      若设置了WNOHANG,若未处理到退出的进程,放回0
      出错,返回-1

若wait,waitpid等待之前,已经有子进程退出,那么会直接进行退出子进程之后的操作,返回子进程ID

	 int main()
 {
   pid_t ret =fork();
   if(ret<0)
   {
     perror("fork error\n");
     return -1;
   }
   else if(ret==0) 
   {
    sleep(3);
    exit(66);
   }
   int exit_code;
   //int res=waitpid(ret,&exit_code,WNOHANG);//非阻塞等待可能会导致僵尸进程
   int res;
   while((res=waitpid(-1,&exit_code,WNOHANG))==0)//非阻塞等待需循环调用直至等到接受子进程退出的返回值,才不会产生僵尸进程
   {
     printf("子进程还没退出,那我等你一哈嘛!!!\n");
     sleep(1);
   }
   if(res>0)//成功获取到子进程pid
   {
       printf("pid: %d exit\n",res);
   }
   else if (res==0)//WNOHANG中子进程已经退出或无进制退出情况
   {
     printf("have no stutas exited!\n");
   }
   else //出错
   {
     perror("error fork");
     return -1;
   }
   while(1)                                                                                                              
   {
     sleep(1);
     printf("parents-------------\n");
   }
   return 0;
 }

在这里插入图片描述

若使用WNOHANG操作选项,是进行循环处理,则可能代码运行至waitpid处,子进程还并未退出,同时WNOHANG是非阻塞操作,进而子进程资源未被释放,导致僵尸进程

3.2、关于进程退出返回值

关于进程退出返回值(正常退出,异常退出则不获取返回值),实际只用了一个字节保存,即只能返回0~255的数据

在这里插入图片描述
进程退出,分为正常退出(exit,main中return),异常退出(中途崩溃)
一个进程异常退出,是没有返回值的,这时候保存返回值的空间中的数据是不可知,获取其返回值也无意义

  • 在wait和waitpid中的输出参数,status,低七位是异常退出码,如果不为0就表示进程异常退出
  • 因此在进程获取返回值之前,应当先获取其异常退出码,若正常,在获取其退出返回值
可通过位移操作:
- 异常退出码:(4字节中低7)status & 0x7f
- 退出返回值:(4字节中低16位的高8位) (status>>8& 0xff
宏函数操作:
- int WIFEXITEND(status); 等价于 status & 0x7f
- int WEXITSTATUS(status);等价于 (status>>8& 0xff

落实于代码中:

if(res>0)
{
	if(exit_code & 0x7f==0)
	{
		printf("pid:%d exited! exit code:%d\n",res,exit_code);
	}
	else
	{
		printf("child abnormal termination!\n");
	}
}

4、进程替换

4.1、概念

  • 前言: 创建一个子进程(pcb),复制了父进程大量的信息,代码共享,可以分担任务处理的压力,但这不是主要目标,主要目标是进行程序替换,让子进程管理另外一个程序的运行
  • 程序替换 :一个新的程序加载到内存中,然后改变一个进程的页表映射信息,将其更改到新的程序的指令和数据上,这时候当前的pcb管理的程序,便不是原来的程序,而是新程序

4.2、程序替换接口


int execl(const char *path, const char *arg, ...);
参数:path:带路径的新程序名称额,arg是参数,是一个不定参,以NULL结尾

int execlp(const char *file, const char *arg,...);
参数:file:不带路径的新程序名称
     不带路径,则默认在path环境变量指定的路径下去找程序,常用于指令程序的替换 

int execle(const char *path, const char *arg ,..., char * const envp[);
参数:path:带路径的新程序名称, arg是参数,envp是环境变量; 

int execv(const char *path, char *const argv[]);
参数:argv[],与execl的区别在于,参数不是一个一个给,而是组织成为字符指针数组一次性表示

int execvp(const char *file, char *const argv[]);
参数:与execlp相比,在指定路径下找程序,参数通过数组表示
~~~总结:
1.l与v,在于参数是一个个列出,还是一数组的形式
2.不带p和带p,在于是否同path环境变量指定路径下去找程序
3.带不带e,即参数中是否需要环境变量

4.3、实例分析

//exec.c程序
#include <stdio.h>
#include <unistd.h>
int main()
{
    printf("hello world\n");
    char* argv[]={"./example","-a","-s","-a",NULL};
    execv("./example",argv);
    printf("hello monkey\n");
  return 0;                                        
}

//example.c程序
 #include <stdio.h>       
 int main()           
 {           
   printf("example()\n");
   return 0;              
 }                        

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

小结:

  • 1.函数若程序替换成功,即修改了页表映射关系,而新的程序加载到内存中,之前的程序则被释放
  • 2.调用出错返回-1
  • 3.exec函数只有出错的返回值而没有成功的返回值

总结

  • 以上的,从进程创建,退出,等待,替换四个方面,讲解了进程控制;
  • 后续也将以一篇mini_shell实现的文章,便于大家更好的去感受进程控制!!!
    文章有不足的地方,也希望大家能多多指点指点~~~~
  • 9
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 7
    评论
评论 7
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值