【Linux编程学习笔记】进程操作

进程

进程和程序

程序:死的,只占用磁盘空间
进程:运行起来的程序,占用内存、cpu等系统资源

并发

任意时刻点只有一个进程在运行,分时复用cpu

单道程序设计

所有进程一个接一个执行

多道程序设计

多道相互独立的程序,相互穿插进行

MMU

在这里插入图片描述

PCB进程控制块

Linux内核的进程控制块是task_struct结构体

  • 进程id。系统中每个进程有唯一的id,在C语言中用pidt类型表示,其实就是一个非负整数。
  • 进程的状态,有就绪、运行、挂起、停止等状态。
  • 进程切换时需要保存和恢复的一些 CPU寄存器。
  • 描述虚拟地址空间的信息。
  • 描述控制终端的信息。
  • 当前工作目录(CurrentWorking Directory)
  • umask 掩码。
  • 文件描述符表,包含很多指向fie 结构体的指针。
  • 和信号相关的信息
  • 用户 id 和组 id
  • 会话(Session)和进程组。
  • 进程可以使用的资源上限(Resource Limit)。

进程状态

在这里插入图片描述

环境变量

PATH:可执行文件搜索路径
SHELL:当前shell,通常为/bin/bash
TERM:当前终端类型
LANG:语言、编码
HOME:当前用户主目录

进程控制

fork 函数

创建一个子进程

pid_t fork(void);
返回值:
	成功:
		父进程:子进程的id
		子进程:0
	失败:
		-1,设置errno
		
pid_t getpid(void);	获得进程id
pid_t getppid(void);	获得父进程id

在这里插入图片描述

进程共享

刚 fork 后
父子相同处:
全局变量、.data、.text、栈、堆、环境变量、用户 ID、宿主目录、进程工作目录、信号处理方式…
父子不同处:
1.进程IDI
2.fork 返回值
3.父进程 ID
4.进程运行时间
5.闹钟(定时器)
6.未决信号集
父子共享的:
文件描述符
mmap建立的映射区(进程间通信)

注意
父子进程遵循读时共享,写时复制的原则——全局变量
躲避父子进程共享全局变量的误区
fork之后父进程先执行还是子进程先执行不确定,取决于内核所使用的调度算法

父子进程gdb调试

使用 gdb调试的时候,gdb只能跟踪一个进程。可以在fork函数调用之前,通过指令设置 gdb,调试工具跟踪父进程或者是跟踪子进程。默认跟踪父进程。

set follow-fork-mode child 命令设置 gdb 在 fork之后跟踪子进程。
set follow-fork-mode parent 设置跟踪父进程。

注意:一定要在fork函数调用之前设置才有效。

exec函数族

fork 创建子进程后执行的是和父进程相同的程序(但有可能执行不同的代码分支),子进程往往要调用一种exec函数以执行另一个程序。当进程调用一种exec函数时,该进程的用户空间代码和数据完全被新程序替换,从新程序的启动例程开始执行。调用exec并不创建新进程,所以调用 exec 前后该进程的id 并未改变。

将当前进程的.text、.data 替换为所要加载的程序的.text、.data,然后让进程从新的.text第一条指令开始执行,但进程ID不变,换核不换壳

其实有六种以exec开头的函数,统称exec函数:

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(fonst char *file, char *const argv[]);
int execvpe(const char *path, char *const argv[], char *const envp[]);
execlp 函数

加载一个进程,借助PATH环境变量
通常用来调用系统程序

int execlp(const char *file, const char *arg, ...);
返回值:
	成功:不返回
	失败:-1,设置errno
示例:
	execlp("ls","ls","-l","-d","-h",NULL); NULL作为哨兵表示结束
execl 函数

加载一个进程,借助路径
通常用来调用自己写的程序

int execl(const char *path, const char *arg, ...);
返回值:
	成功:不返回
	失败:-1,设置errno
示例:
	execl("./a.out","./a.out",参数,NULL);
	execl("/bin/ls","ls","-l","-d","-h",NULL);
execvp 函数

加载一个进程,借助自定义环境变量env

int execvp(fonst char *file, char *const argv[]);
示例:
	char *argv[]={"ls","-l","-d","-h",NULL};
	execvp("ls",argv);

回收子进程

孤儿进程

父进程先于子进程结束,则子进程成为孤儿进程,子进程的父进程成为init进程,称为init进程领养孤儿进程。

僵尸进程

进程终止,父进程尚未回收,子进程残留资源(PCB)存放于内核中,变成僵尸(Zombie)进程。

特别注意,僵尸进程是不能使用 kill 命令清除掉的。因为kill命令只是用来终止进程的,而僵尸进程已经终止。
思考! 用什么办法可清除掉僵尸进程呢?——将父进程kill

wait 函数

父进程调用 wait 函数可以回收子进程终止信息。该函数有三个功能:

  1. 阻塞等待子进程退出
  2. 回收子进程残留资源
  3. 获取子进程结束状态(退出原因)
pid_t wait(int*status);

返回值:
	成功:回收子进程的ID;
	失败:-1(没有子进程)
	
宏函数:
	WIFEXITED:为真,说明子进程正常终止
	WEXITSTATUS:若WIFEXITED为真,获取子进程的退出状态
	
	WIFSIGNAL:为真,说明子进程异常终止
	WTERMSIG:若WIFSIGNAL为真,获得使子进程终止的信号的编号
	
	WIFSTOPPED:为真,说明子进程处于暂停状态
	WSTOPSIG:若WIFSTOPPED为真,获得使子进程暂停的信号的编号
	WIFCONTINUED:为真,说明子进程暂停后已经继续运行

示例:
	if(WIFEXITED(status)){  //为真,说明子进程正常终止
    	cout<<"child exit with"<<WEXITSTATUS(status)<<endl;
    }
    if(WIFSIGNALED(status)){    //为真,说明子进程异常终止
    	cout<<"child kill with signal "<<WTERMSIG(status)<<endl;
    }

waitpid 函数

pid_t waitpid(pid_t pid, int *status, int options);

参数:
	pid:
		>0 回收指定id的子进程
		-1 回收任意子进程
		0 回收和当前调用waitpid一个组的所有子进程
		<-1 回收指定进程组内的任意子进程(进程组号取反)
	status:(传出)回收子进程的状态
	options:
		WNOHANG 指定回收方式为非阻塞

返回值:
	>0:成功回收的子进程id
	0:函数调用时,参数3指定了WNOHANG,并且,没有子进程结束
	-1:失败,设置errno

总结
wait 和 waitpid 一次只能回收一个进程
想回收多个,循环回收

while((wpid=waitpid(-1,NULL,0))!=-1){
	cout<<"wait child "<<wpid<<endl;
}
  • 13
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

一往而情深

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值