Linux系统编程(中)

进程创建

fork()

pid_fork(void):通过复制父进程来创建一个新的子进程(代码共享、数据独有)
返回值:
父进程返回新创建的子进程的pid;
子进程返回 0;
出错返回 -1;

写时拷贝技术:
提升子进程创建效率,避免不必要的内存消耗。
子进程复制了父进程中大部分信息,因此子进程有自己的变量,但是自己的变量经过页表映射之后与父进程变量访问的是同一块物理内存,当这块内存空间中数据即将要被修改时,则给子进程重新开辟新空间,并拷贝数据过去。
(每个进程的变量都应该有自己的存储空间,这样才能保证进程间的独立性)

malloc 开辟空间时,只是创建的虚拟地址空间,只有需要进行变量修改时才会映射到物理内存空间。

vfork()

pid_t vfork(viod)
创建一个子进程,父子进程共用一块虚拟地址空间
create a child process and block parent — 创建一个子进程并阻塞父进程直到自己 exit 退出,或程序替换之后父进程才会运行

fork 创建子进程之后,父子进程谁先运行不一定-----看系统调度安排
vfork 创建子进程,一定是子进程先运行,只有子进程退出或程序替换之后父进程才会继续运行(父子进程共用一个虚拟地址空间,则意味着用的是同一个栈-------栈混乱)

进程终止

退出:终止一个进程

方式一:return

main 函数中 return (仅在 main 函数中使用 return)

return 终止一个函数,并返回一个数据,main 函数是程序入口函数,入口函数一旦退出则程序终止。

在这里插入图片描述

方式二:库函数 exit()

库函数 void exit(int retval)

任意位置调用接口则程序退出
在这里插入图片描述
使用示例:
在这里插入图片描述

在这里插入图片描述

注意:
在修改 exit.c 文件之后进行保存退出时,提示错误信息 .swap 文件,此时需要删除指定的交换文件 .swap 即可:
在这里插入图片描述

man 手册:
man 1:命令
man 2:系统调用接口
man 3:库函数

使用示例:

在这里插入图片描述
在这里插入图片描述在这里插入图片描述

系统调用接口:
操作系统向上层提供的用于访问内核的接口,功能相对单一
库函数:
对系统调用接口进行封装,封装出了适用于典型场景的库函数

方式三:系统调用接口 _exit()

系统调用接口:void _exit(int retval)
在任意位置调用,都可以退出程序运行

在这里插入图片描述

使用示例:
在这里插入图片描述

在这里插入图片描述

exit 与 _exit 的区别:
退出程序运行前是否会将缓冲区中的数据进行刷新写入文件中
(printf : \n 具有刷新缓冲区的作用 )

进程退出返回值的意义:
return 以及 exit 给与的数据其实就是进程退出码;进程退出码用来表示当前进程任务处理的结果。

打印错误信息的接口

void perror(const char* msg);
用来打印上一步系统调用接口使用失败的原因信息

读取一个文件信息
FILE *fopen( const char *filename, const char mode );
FILE
fp=fopen(“文件名”,“读取方式”)

在这里插入图片描述

const char* strerror(int errno);
根据错误编号,返回对应编号的字符串错误原因

extern int errno;
在进程 pcb 中有个全局变量int errno;
每个系统调用接口在执行返回的时候都会重置 errno (错误编号)

进程等待

父进程在创建子进程之后,等待子进程退出,获取子进程退出码,释放子进程资源,避免出现僵尸进程。

wait

pid_t wait(int* status);
(1)wait 是一个阻塞接口,功能是等待当前调用者的任意一个子进程退出,获取返回值,释放资源(若已经有退出的-------直接进行处理)
(2) status 参数,是一个int空间的地址,用于向指定空间中存放子进程退出返回值
(3)wait 函数返回值,成功返回处理的退出子进程的pid;失败返回-1

示例:

我们先写一个 wait 等待程序,使得父进程获取任意一个子进程退出的相关信息(退出返回值):

  1 #include<stdio.h>
  2 #include<stdlib.h>
  3 #include<unistd.h>
  4 #include<sys/wait.h>  //wait 头文件
  
  5 int main()                                                                 
  6 {
  7   pid_t ret =  fork();   //创建一个子进程
  8 
  9   if(ret < 0){
 10     perror("fork error!");   //创建子进程失败
 11     exit(0);    //设置退出码
 12   }
 13   else if(ret==0){
 14     //子进程
 15     sleep(3);    
 16     exit(99);
 17   }
 18   else{
 19     //父进程
 20 
 21     int status;
 22     //父进程等待,wait 等待任意一个子进程退出,获取它的退出返回值
 
 23     ret=wait(&status);   //获取退出的子进程相关信息
 
 24     //不存在子进程退出
 25     if(ret<0){
 26       perror("childfork error!\n");
 27       return -1;
 28     }
 29 
 30     //存在子进程退出
 31    printf("%d 子进程退出了~~,返回值是 : %d\n",ret ,status);
 32  }
 33 return 0;
 34}

执行该代码:
在这里插入图片描述
重新开一个 SSH 窗口,可以查看到相关的进程等待信息:

在这里插入图片描述

阻塞接口

为了完成一个功能发起的一个调用,但是这个调用完成条件不具备,则接口一直等待不返回;

非阻塞接口

为了完成一个功能发起一个调用,但是这个调用完成条件不具备,则立即报错返回。

waitpid

pid_t waitpid(pid_t pid,int* status,int options);
(1)waitpid 既可以等待任意一个子进程退出 pid=-1(第一个参数为 -1),也可以等待指定的子进程退出 pid>0(第一个参数 >0)
(2)pid 参数:>0 表示等待指定 pid 子进程退出;-1 表示等待任意一个子进程退出
(3)waitpid 既可以阻塞等待,也可以使用非阻塞等待
(4)options:0-默认阻塞等待,WNOHANG-设置为非阻塞(当前没有子进程退出则会立即报错返回)
(5)返回值:成功返回处理的退出子进程 pid ,若没有子进程退出返回 0 ,出错返回 -1

示例:

(1)使用 waitpid 等待任意子进程退出-阻塞等待

ret=waitpid(-1,&status,0);

 1 #include<stdio.h>                                                            
  2 #include<stdlib.h>
  3 #include<sys/wait.h>  //waitpid 的头文件
  4 #include<unistd.h>
  5 
  6 int main()
  7 {
  8   pid_t cpid=fork();  //创建一个子进程
  9 
 10   if(cpid<0){
 11     perror("fork error~!\n");  //创建子进程失败
 12     exit(0);
 13   }
 14   else if(cpid==0){
 15     //child
 16     sleep(3);
 17     exit(99);
 18   }
 19   else{
 20     //父进程
 21 
 22     int status,ret;
 23     ret=waitpid(-1,&status,0);  //此时表示等待任意一个子进程退出---阻塞
 24 
 25     //waitpid(pid_t pid,int* status,int options)
 26     //pid = -1 表示等待任意一个子进程退出,当第一个参数 pid=-1时 waitpid == wait 的使用; pid>0 表示等待指定子进程退出
 27     //0 表示阻塞等待,WNOHANG-非阻塞等待
 28     
 29 
 30     //没有子进程退出
 31     if(ret<0){
 32       printf("childfork error~!\n");
 33       return -1;
 34     }
 35 
 36     //存在子进程正常退出
 37     printf("%d 子进程退出了~~~,退出返回值为 : %d\n",ret,status);
 38   }
 39 
 40   return 0;
 41 }        
 

运行结果:

在这里插入图片描述
在这里插入图片描述

(2)非阻塞等待时:

ret=waitpid(-1,&status,WNOHANG);

通常使用 waitpid 时会采用循环方式进行等待:
WNOHANG 表示非阻塞等待

25     while((ret=waitpid(-1,&status,WNOHANG))==0){                       
26         printf("还没有子进程退出,我先去干别的事儿~~\n");             
27         sleep(2);
28   }

运行结果:

在这里插入图片描述
在这里插入图片描述

(3)waitpid 等待指定进程退出时:

ret=waitpid(pid,&status,0);
阻塞等待子进程 pid ( > 0 ) 退出

在这里插入图片描述

假如我们手动终止一个进程:

在这里插入图片描述

总结说明:

阻塞操作流程较简单,但是资源利用率较低;非阻塞操作流程稍微复杂(通常使用循环操作),但是对资源利用率较高

在这里插入图片描述

通过 wait 获取的返回值有多个信息,其中进程的退出码只是其中一部分,并且只用一个字节(8 byte)来进行保存(若 exit 给定的退出码过大,则会发生截断)
coredump — 核心转储,当程序异常退出时,保存程序运行信息,以便于能够事后调试,默认处于关闭状态(隐私&安全,占据磁盘空间)

编写一个代码来测试 coredump:

6	int main()
7	{
8	  int* p=NULL;     //野指针----错误
9	  *p=10;
10	  printf("%d\n",*p);
11	  return 0;
12	}
13	

默认情况下 core dump是关闭状态的:
在这里插入图片描述

修改 coredump 值之后调试 test 程序:

在这里插入图片描述

测试完成之后,需要将原来的 core dump 改回默认值:ulimit -c 0

在这里插入图片描述

当程序正常退出时—经过return 或 exit 退出,都是设置了退出码的,若程序在运行途中异常崩溃的则保存的退出码无意义—没有经过设置

异常信号值----保存异常退出原因:
0-程序正常退出,!0-异常退出
因此要获取一个进程的退出码,首先要确定这个进程是否是正常退出的,若是才有意义:
status & 0x7f --------- 判断低七位的异常信号值是否为0(正常退出),若是表示正常退出,则 status 的低 16 位中高 8 位表示的是退出码(status >> 8)& 0xff

示例:

  1 #include<stdio.h>
  2 #include<stdlib.h>                                                           
  3 #include<unistd.h>
  4 #include<sys/wait.h>
  5 
  6 int main()
  7 {
  8   //创建子进程
  9   pid_t cpid=fork();
 10 
 11   if(cpid<0){   //创建子进程失败
 12     perror("fork error~!\n");
 13     exit(0);
 14   }
 15   else if(cpid==0){
 16     //child
 17     sleep(100);    //100 s 之后子进程会退出,此时手动删除一个进程会产生异常退出信息;
 // sleep(3) ;  // 3s 之后子进程退出,此时父进程获取子进程正常退出信息并输出相应退出信息
 18     exit(99);
 19   }
 20 
 21   //父进程
 22   
 23   int status;
 24   int  ret;
 25   //waitpid 非阻塞等待任意一个子进程
 26   while((ret=waitpid(-1,&status,WNOHANG)==0)){
 27     printf("子程序还没有退出~~~!\n");
 28     sleep(2);
 29   }
 30 
 31   if(ret<0){
  32     perror("fork error!\n");
 33     return -1;
 34   }
 35 
 36   //取出 static 中 低 7 位,判断低七位的异常信号值是否为 0 ,0-正常退出,!0-异常退出
 37   //返回值为 status 中低16位中的高八位
 38   
 39   if((status & 0x7f)==0){
 40     //判断 static 变量的低七位是否为0 ---- 程序是否正常退出
 41     printf("%d 子进程退出了!返回值是 : %d\n",ret,(status >> 8) & 0xff);
 42   }
 43   else{
 44     printf("子进程异常退出!\n");
 45   }
 46 
 47   sleep(10000);
 48   return 0;
 49 }                 

运行结果:

(1)正常退出:

直接会返回正常退出的子进程信息

while((ret = waitpid(-1,&status,WNOHANG)==0); ------- 表示当前还没有子进程退出,因此会循环等待

在这里插入图片描述

非阻塞等待常采用 while 循环来进行等待,否则程序执行会直接报错返回:

waitpid(-1,&status,WNOHANG);

此时运行结果,0 子进程退出,表明没有子进程退出,waitpid 不会一直等待子进程的退出因此运行处理的结果是不正确的

在这里插入图片描述

(2)异常退出示例:

kill 进程编号 ---------- 手动终止一个进程

在这里插入图片描述

程序替换

pcb :描述程序的运行过程

为什么创建子进程?
创建一个子进程是为了完成一段任务;
(1)做与父进程相同的事情–压力分摊
(2)完成另一个任务(执行另一段代码 if(fork()==0){…//子进程 })
但是若把另一段代码与当前代码合在一起,会导致程序特别庞大,且模块化不太好

程序替换概念

替换掉一个 pcb 所描述的要调度管理的程序,替换成另一个程序
即 将一个新程序加载到内存中,然后修改当前 pcb 进程的页表映射信息,初始化地址空间,让其调度管理新程序。

#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>

int main()
{
	printf("这是我原来的程序!!!\n");
	//程序替换
	execlp("ls","ls","-l",NULL);
	printf("这是我原来的程序结束!!!\n");

	return 0;
}

在这里插入图片描述

该程序的运行结果为:

在这里插入图片描述

实际上运行到程序替换之后就会结束,不会运行第二个 printf 信息,因为替换之后相当于运行新的替换之后的程序,原程序自动释放。

程序替换之后,相当于对于 pcb 来说,调度了一个新程序(不关心以前的程序–被释放),新程序运行完毕, 就会退出

ps:有任何问题欢迎评论留言呀~~

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值