Linux之系统编程之初识进程

一、进程概念

1.什么是进程

  • 进程就是指运行中的程序。在操作系统来看,进程是对一个运行中程序的描述(PCB),Linux下,这个pcb实际是一个结构体struct task_struct{,};。(站在操作系统角度,进程就是pcb)
  • 任务管理器中的后台进程叫批处理进程,有的操作系统将进程称为任务。
  • 进程如何描述一个运行中程序(一个pcb中包括):① 内存指针。CPU通过pcb中的内存指针在内存中找到运行中的程序,再通过CPU分时机制实现多个程序运行。(CPU分时机制:每个程序在CPU上运行都有一个时间片。时间片:程序在CPU给你运行的这段时间,运行完毕后则调度切换。) ② 程序计数器。保存一个程序上一次执行的指令。 ③ 上下文数据。保存一个程序上一次处理的数据。 ④ 程序标识符PID ⑤ 状态 ⑥ 优先级。交互式程序要求的优先级一般最高,批处理进程的优先级会低一点。 ⑦ 记账信息。保存一个程序在CPU上执行时间。 ⑧ IO信息

2.如何查看进程

  • ps :查看进程。
  • ps-aux :查看所有进程详细信息。
  • ps-ef :查看所有的进程信息。
  • vim下===》 pid_t getpid(void) : 获取调用进程的ID。 printf("%d\n", getpid);
  • void exit(int status); : 退出进程。

3.进程状态

  • ‘+’ :表示前台进程(依赖于终端,终端退出了该进程就退出了)
  • ‘S’ :表示休眠状态,即使有usleep(1)依然是S状态,因为它运行太快,大部分时间都是处于休眠状态。
  • Linux下的进程状态:
状态名称状态描述
运行态(R)包括运行态和就绪态。只要能拿到时间片之后就能运行或者正在运行的程序都是运行态。
可中断休眠状态(S)可以叫醒,可以被中断信息打断休眠状态的或者休眠时间到了会继续运行的程序。
不可中断休眠状态(D)IO的时候,只能等自己醒,自然醒。
僵死状态(Z)进程已经退出,但是资源没有完全释放。
死亡状态(X)一个进程退出了,销毁资源的时候
停止状态(T)什么都没有干。
追踪状态(t)正在被追踪。
  • 杀死一个进程的命令(即将一个进程变为T状态(停止状态)):kill [进程ID]
  • 强制杀死一个进程的命令(直接销毁一个进程):kill -9 [PID]

3.僵尸进程

  • 僵尸进程是处于僵死状态的进程。
  • 僵尸进程的产生:子进程先于父进程退出,为了保存退出原因,因此资源没有完全被释放,因此,在子进程退出时,操作系统会通知父进程,让父进程获取子进程的退出原因,然后释放子进程所有资源。如果父进程并没有关注子进程退出状态,则子进程称为僵尸进程。
  • 子进程危害;资源(内存资源)泄漏。
  • 僵尸进程是杀不死的,只能避免产生。但是杀死父进程,僵尸进程也就死了。
  • 僵尸进程的避免:进程等待。
  • 僵尸进程的处理:退出父进程。

4.特殊进程

  • ① 孤儿进程:父进程先于子进程退出,子进程变为孤儿进程。孤儿进程是后台进程,运行在后台。父进程变为1号进程(Init进程)。(孤儿进程退出不会成为僵尸进程,因为1号进程时刻关注孤儿进程。)
  • ② 守护进程:也叫精灵进程,是特殊的孤儿进程。在孤儿进程的基础上,脱离终端脱离登陆会话。通常运行在后台,默默工作,不希望收到终端会话的影响。一般以’d’结尾的进程都是守护进程。

5.进程的优先级

  • 进程优先级决定进程CPU资源的优先分配权。

6.环境变量

  • 概念:存放系统运行环境参数的变量。
  • 命令:① echo:除了可以打印字符串,还可以打印变量信息。 ② env:查看所有环境变量。 ③ 打印PATH环境变量:echo $PATH直接打印一个指定环境变量内容。 ④ set:查看所有变量包括环境变量在内。 ⑤ MYSET = 10:设置一个普通的变量。打印是echo $MYSET。 ⑥ unset:删除一个环境变量。unset MYSET.。
  • 目的:① 让系统运行环境参数配置起来更加灵活。 ② 环境变量具有全局特性。

7.程序地址空间(进程的虚拟地址空间)

  • 程序中获取的变量地址都是虚拟的。
  • 每个程序运行需要连续的地址导致对内的利用率低,缺乏内存的访问控制。
  • 0号虚拟地址不可读也不可写(NULL)。
  • 虚拟地址空间:是操作系统为进程所描述的假的地址空间,目的是为了让进程认为自己拥有一块连续的线性的完整的地址空间。但实际上,一个进程使用的内存并非连续存储的,而是通过页表映射了虚拟地址空间与物理地址空间之间的关系,让进程通过页表来获取物理地址,进而实现数据的存储。
  • 作用:① 提高内存利用率,实现物理内存的离散存储。 ② 保证了进程独立性,每个进程都只能访问自己虚拟地址映射的物理内存。 ③ 页表可以进行内存访问控制,页表可以对每个虚拟地址进行权限标记。
  • 虚拟地址空间时通过结构体进行描述的,struct mm_struct{};即内存指针,故有时也称为内存描述符。

.8.内存管理

  • 分段式:对内存比较友好,但内存利用率不高。地址组成:段号 + 段内偏移。
  • 分页式:提高内存利用率。地址组成:页号 + 页号偏移。
  • 段页式:目前采用的方案。首先地址采用分段式,每一段内采用分页式。地址组成:段号 + 段内页号(得到物理块号) + 页内偏移。
  • 分页式内存管理计算:① 页号 = 虚拟地址 / 页面大小,再对应页表找到块号。 ② 页内偏移 = 虚拟地址 % 页面大小。 ③ 物理地址 = 块号 * 块大小 + 页内偏移。
  • 页表的作用:映射虚拟地址与物理地址的关系。内存访问控制。

二、进程控制

1.进程创建

(1) pid_t fork(void) 通过复制父进程,创建子进程。

  • 通过复制调用创建一个子进程,子进程因为拷贝了父进程pcb里面的很多数据,因此与父进程内存指针以及程序计数器相同,所以运行的代码以及运行的位置都一样。
  • 返回值有三种(用于分辨父子进程,进行代码分流):① -1:创建子进程失败。 ② ==0:对于子进程返回值是0。 ③ >0:对于父进程,返回值是子进程的PID。
  • 子进程有唯一的自己的ID,被复制的称为父进程。
  • 写时拷贝技术:提高进程的创建效率。当父进程将数据更改时之前会先为子进程开辟一块内存,再将数据拷贝,最后再更改父进程数据。
  • 创建一个子进程的过程(拷贝数据的时候是用写时拷贝技术):① 创建pcb。② 拷贝父进程pcb中的数据(拥有相同的虚拟地址空间,相同的页表)。 ③ 父子进程一开始映射相同的一款物理内存。 ④ 等物理内存修改的时候才为子进程重新开辟内存,拷贝数据过来。(要求进程的独立性)
  • 父子进程代码共享,数据独有:因为代码段是只读的,不会修改,因此是一直映射用一块物理内存。

(2)pid_t vfork(void)

  • 创建一个子进程,父子进程共用同一个虚拟地址空间,为了避免出现调用栈紊乱,因此vfork创建子进程之后父进程会阻塞,直到子进程退出或者程序替换。

(3)fork和vfork两个接口创建子进程都是内核中通过调用clone函数实现的,在clone函数重实现task_struct{};的创建,以及根据参数不同,拷贝不同的数据。(例如vfork就不需要为子进程创建虚拟地址空间、页表)

2.进程终止

  • 进程终止场景:① 异常退出。 ② 正常退出,结果符合预期。 ③ 正常退出,结果不符合预期。
  • 获取一个系统调用的错误信息(#include <errrno.h>):① void perror(char* message)打印之前最近的系统调用的错误信息。 ② char* strerro(int errno)获取错误编号对应的字符串错误信息。
  • 如何终止一个进程:① 在main函数中用return,在退出时会刷新缓冲区。 ② 在任意地方调用exit(int statu)接口,为库函数接口,在退出前会刷新缓冲区。 ③ 在任意地方调用_exit(int statu)接口,为系统调用接口,退出时会直接释放所有资源。

3.进程等待

  • 等待子进程的退出,获取此子进程的返回值,并回收子进程的所有资源,避免产生僵尸进程。

(1)int wait(int* statu);

  • wait是一个阻塞函数,为了等待任意一个子进程退出,若子进程没有退出则一直等待,子进程的返回值会放到传入的参数statu中。
  • 阻塞:为了完成一个功能,发起调用,若当前不具备完成条件,则一直等待直到完成后返回。
  • 非阻塞:为了完成一个功能,发起调用,若当前不具备完成条件,则立即报错返回。

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

  • 可等待指定pid子进程退出。
  • options若被指定为WNOHANG,则将waitpid设置为非阻塞。
  • pid:-1表示等待任意一个子进程; >0表示等待指定pid的子进程。
  • 返回值:① >0返回是退出子进程的pid。 ② ==0表示没有子进程退出。 ③ <0表示等待出错。
  • status的组成:低16位有效;其中高8位存放返回值,低8位中的低7位存着异常信号,数&0x7f,高1位coredump标志(异常信息时是否进行核心转储,保存运行信息,好处是方便时候调试)。
  • WIFEXITED(status): ==if(status & 0x7f),看是否为异常退出。
  • WEXITSTATUS(status): ==(status >> 8)0xff返回异常信息。

4.程序替换

  • 替换一个进程正在运行的程序,pcb还是原来的pcb,其中代码和数据替换了。
  • 进程替换运行的程序后,会初始化虚拟地址空间的代码段和数据段,并且更新页表信息。
  • 进行程序替换:exec函数族
//等价于直接运行ls
int execl(const char* path, const char* arg...)
int main()
{
	execl("/bin/ls", "ls", "-l", "-a", NULL);
	printf("hello Linux\n");
	return 0;
}

//l和v的区别:程序运行参数的赋值方式不同
int execv(const char* path, char* const argv[])
int main()
{
	char* argv[32] = {NULL};
	argv[0] = "ls";
	argv[1] = "-l";
	argv[2] = "-a";
	argv[3] = "NULL";
	execv("/bin/ls", argv);
	printf("hello Linux\n");
	return 0;
}

//带e和不带e的区别:新程序的环境变量是否由自己来进行赋值的。
//带e是自己设置环境变量,不带e是用原来的环境变量,子进程的环境变量实际上都是父进程给的。
int main()
{
	if(execve("./env", argv, NULL) < 0)
	{
		perror("execv error");
		return -1;
	}
	printf("hello Linux\n");
	return 0;
}

//带p和不带p的区别:在于第一个参数,带p是可执行程序文件的路径名

execl(char* path, char* argv, ...);
execlp(const char* file, char* argv, ...);
execle(char* path, char* arg, ..., char* env[]);

execv(char* path, char* argv[]);
execvp(char* file, char* argv[]);
execve(char* path, char* const argv[], char* const envp[]);
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值