Linux进程控制详解.

🧸🧸🧸各位大佬大家好,我是猪皮兄弟🧸🧸🧸
在这里插入图片描述

一、进程创建

#include <unistd.h>
pid_t id = fork();

1.创建子进程,OS做的事

进程=内核数据结构+进程数据和代码

  1. 分配新的内存块和数据结构给子进程
  2. 将父进程部分数据和内容进行拷贝
  3. 添加子进程到系统进程列表
  4. fork()返回,开始调度器调度

理论上,子进程也要拥有自己的代码和数据,但是一般而言,子进程没有 加载的过程,所以,子进程没有自己的代码和数据,只有靠父进程

2.写时拷贝

代码是不可被写的,只能读取,所以父子可以共享
数据是可被修改的,所以必须分开(修改的时候才进行)

如果说创建进程的时候,就直接拷贝父进程的数据,那么可能子进程根本不会去用或者只是去读取,浪费空间。
1.因为即便是OS也无法提前知道哪些空间会被提前写入,所以无法提前拷贝会被写入的那些空间
2.提前拷贝了,会立马使用吗?
所以OS选择了写时拷贝技术来实现父子进程数据的分离

1.用的时候再分配是高效使用内存的表现
2.操作系统无法预知被访问的空间

因为有写时拷贝的存在,所以,父子进程得以彻底分开,保证了进程的独立性。写时拷贝是一种延迟申请的技术,可以提高整机内存的使用率。

3.上下文数据

创建子进程之后,父子进程是所有代码共享的(不仅仅是fork()之后的代码)

进程随时可能被中断,下次回来因为必须从之前的位置继续执行,就要求CPU随时记录当前进程执行的位置,所以,CPU内由对应的寄存器数据,用来记录当前进程的执行位置,这个寄存器叫EIP(也叫做PC,程序计数器),记录的是下一条执行的代码,还有其他寄存器来记录其他状态,这些寄存器中的数据,我们叫做进程的上下文数据,创建的时候,上下文数据也是要给子进程的,所以这是的执行状态,父子进程相同,才觉得子进程只有fork()之下的代码,其实是全部代码

4.fork调用失败的原因

1.系统中已经有太多的进程
2.实际用户的进程数超过了限制(一般的用户不会允许创建太多的进程)

二、进程终止

进程终止,OS释放进程申请的相关内核数据结构和对应的代码和数据
进程终止的常见方式

a.代码跑完,结果正确
b.代码跑完,结果不正确
c.代码没有跑完,程序崩溃

1.main函数的返回值

main函数的返回值我们总是写为return 0;但是其实他是有意义的,main函数的返回值并不总是0,可以是其他值,main函数的返回值叫做进程退出码,如果进程退出码为0(success),表示代码结果是正确的,不正确就是非0,main的返回值是用来返回给上一级进程的,用来评判该进程执行结果用的,我们可以忽略

echo $?  来查看最近一次进程执行完毕之后的退出码

在这里插入图片描述
在这里插入图片描述
由上所述,直接返回0其实是不对的,应该对于函数中的执行结果设计不同的返回值,通过进程退出码来判定main函数执行结果的正确性(非零值有无数个,不同的非零值就可以表示不同的原因,方便定位错误的原因细节)

2.strerror

将进程退出码的含义进行打印
在这里插入图片描述

printf(strerror(num));
//用这个来查看每个进程退出码所代表的函数,写个for循环

在这里插入图片描述
程序崩溃的时候,退出码无意义!因为退出码对应的return没有执行

exit && _exit

exit

a.return语句,就是终止进程的,但是只有在main函数才是终止进程,其他函数是 函数返回。
b.exit

#include <stdlib.h>
int exit(int status)

引起正常进程终止,能达到和return一样的效果,但是,exit在任何地方都可以调用,都表示终止进程,所以,如果想终止进程,推荐用exit

_exit

除了exit外,还有_exit,这是一个系统调用接口
在这里插入图片描述

区别

exit:是一个库函数
1.执行用户定义的清理函数
2.冲刷缓冲,关闭流等
3.进程退出

_exit:是一个系统调用
直接进程退出

这里也可以看出来,缓冲区不再操作系统内部,另外,缓冲区是C标准库维护的

三、进程等待

1.wait

在这里插入图片描述
等待进程状态变化,wait是阻塞式的等,一直等到子进程状态变化

#include <sys/types.h>
#include <sys/wait.h>
pid_t wait(int*status)
//status:输出型参数,不关心子进程退出结果则status为NULL
//					关心子进程退出结果则status为&status
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <stdlib.h>

#include <sys/types.h>
#include <sys/wait.h>
int main()
{
	pid_t id = fork();
	if(id<0)
	{
		perror("fork");
		exit(1);//退出码为1,不正确
	}
	else if(id==0)
	{
		int cnt=5;
		while(cnt)
		{
			printf("cnt: %d,im child process: pid:%d, ppid:%d\n",
			cnt,getpid(),getppid());
			sleep(1);
			cnt--;
		}
		exit(0);
	}
	else
	{
		//父
		printf("im parent process:pid:%d, ppid:%d\n",
		getpid(),getppid());
		sleep(7);
		pid_t ret =wait(NULL);
		if(ret>0)
		{
			printf("等待子进程成功,ret:,%d\n",ret);
		}
		while(1)
		{
			printf("im parent process: pid:%d,ppid:%d\n",
			getpid(),getppid());
			sleep(1);
		}
	}
	
	return 0;	
}

在这里插入图片描述
可以看出,父进程一直在等,直到子进程有了结果才继续运行

2.waipid

#include <sys/types.h>
#include <sys/wait.h>
pid_t waitpid(pid_t pid,int*status,int options);
//pid =-1,等任意一个子进程
//waitpid(pid,NULL,0) 等于 wait(NULL)
//pid>0,等待进程ID与pid相等的子进程
//options默认为0,表示阻塞等待
//status:输出型参数,不关心子进程退出结果则status为NULL
//					关心子进程退出结果则status为&status

status的构成
status并不是按照整数来整体使用的,而是按照bit位的方式,将32个比特位进行划分,只需要知道 低16位即可

我们kill -l知道 是没有0号信号的
在这里插入图片描述
进程异常退出 就是操纵系统以发送信号的方式杀掉了该进程
status的低7位是表示进程收到的信号(无0信号,0说明 是正常跑完的,运行期间没有收到信号)

status的次低8位才是子进程退出码(异常退出进程退出码无意义)

(status>>8)&OxFF//第八位,F要用四个bit位来表示(1111),所以低八位&0xFF
int status;
pid_t ret = waitpid(id,&status,0);
printf("等待子进程成功,ret:%d,子进程退出码:%d  \n",ret,(status>>8)&0xFF);
//ret是wait/waitpid的返回值,正常退出进程退出码才有意义,程序崩溃的话进程退出码
//是没有意义的。

3.wait/waitpid凭什么拿到子进程退出码

僵尸进程:是一个已死的进程,可以释放数据,至少要保留该进程的PCB信息,task_struct里面保留了任何进程退出时的退出信息,wait/waitpid就是读取子进程的task_struct结构。

//内核源代码task_struct中我们可以看到两个字段
int exit_code,exit_signal;//退出码和退出信号

task_strcut是内核数据结构,而wait/waitpid是系统调用,当然有权力读取到

4. waitpid参数构成

1.pid
id>0:等待指定进程退出
id==-1:等待任意进程退出
(wait其实是waitpid的子集)

pid_t ret = wait(NULL);
pid_t ret = wait(id/*子进程pid,创建子进程时的返回值*/,NULL,0/*阻塞等待*/);

2.status
对于status来说,还要自己去位运算,是不是太麻烦了,所以系统提供了宏

WIFEXITED(statis)查看进程是否正常退出
WEXITSTATUS(status)查看进程的退出码

3.options
默认是0,是阻塞等待
阻塞的时候,父进程是什么都不敢的,如果想让他做点事呢?所以 可以将options设置为WNOHUNG,父进程就是非阻塞等待

5.阻塞等待&&非阻塞等待

阻塞等待,父进程后面的代码不执行,当条件满足的时候,EIP寄存器(PC指针)指向的下一行代码开始唤醒。

非阻塞等待如果父进程检测子进程的退出状态,发现子进程没有退出,通过调用waitpid来等待,如果子进程没有退出,waitpid立马返回(阻塞等待在等待完成后才返回)

非阻塞等待,标志位是WNOHANG(宏),子进程没退出就直接return,直接后面的代码,通过 多次调用waitpid来完成(基于非阻塞调用的轮询检测方案),面对IO的东西太多,用阻塞等待的话,系统就太慢了

四、进程程序替换

程序替换不是所有的代码和数据都被替换掉,比如环境变量就没有被进行替换。(可以理解为,和系统相关的东西没有被替换,无关的都被替换掉)
子进程不想和父进程共享代码,想执行一段自己的代码–>进程程序替换
程序替换是通过特定的机构,加载磁盘上的一个全新的程序(数据和代码),加载到调用进程的地址空间中
在这里插入图片描述

1.进程程序替换操作 exec系列函数

execl

int execl(const char*path,const char*arg,...);
//可以想象成list,传参的时候传一串

第一个参数是找到程序
后面是可变参数列表,命令行上怎么执行,这里就怎么填
并且最后一个参数必须以NULL结尾,表示参数传递完毕

不创建子进程

在这里插入图片描述

int main()
{
	printf("当前进程的开始代码\n");
	//execl("/usr/bin/ls","ls","-l","-a",NULL);
	execl("/usr/bin/top","top",NULL);
	printf("当前进程的结束代码\n");
}

在这里插入图片描述
可以看到代码已经被替换,后面的代码不再执行
在这里插入图片描述
ls是带自由配色选项的,我们也可以加上这个选项 --color=auto

创建子进程

创建子进程 ,我们就可以替换子进程而不影响父进程(进程具有独立性)。我能需要让父进程聚焦在读取数据,解析数据,指派进程执行代码(子进程)的功能上

execv

int execv()const char*path,char*const argv[]);
//可以想象成vector

execl和execv本质上没有区别,只是在传参方式上有所不同

#define NUM 16
...
char*const _argv[NUM]={"ls","-a","-l",NULL};
...
execv("/usr/bin/ls",_argv);

execlp

int execlp(const char*file,const char*arg,...);

exec系列函数,命名中带p的就是在环境中PATH中进行查找

execlp("ls"/*在Path中找到ls*/,"ls","-a","-l",NULL);

execle

int execle(const char*path,const char*arg,...,char*const  envp[]);
//e表示环境变量,最后这个参数不传也是允许的
#define NUM 16
char*_env[NUM]={(char*)"MY_VAL=888777666555","NULL"};
execle(path,参数,参数...,_env/*可不传*/);

2.如何替换我自己写的程序

const char*mypath = "XXX路径";
//可以从工作目录开始,也可以从当前目录开始
...
execl("mypath","mycmd"/*自己写的程序*/,"-a"NULL);
//-a参数,main是可以接收参数的

3.execve

在这里插入图片描述
上面6个exec系列函数底层是调用的同一个接口,为了满足不同的调用场景而实现了不同的封装,execve才是真正的系统调用接口

在这里插入图片描述

  • 3
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 3
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

猪皮兄弟

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

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

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

打赏作者

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

抵扣说明:

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

余额充值