重点:
- 进程的概念
- 进程的内存映像
- 进程控制操作
进程控制
在进程之前首先有并发与并行的答题概念:
①并发:在一个时间段上,CPU处理不同的进程。并发是一个时间段上的概念
②并行:在一个时间点,CPU做同一个事情
多进程的实现:这里假设为单核,这要讨论并发。CPU在一个时间段内,处理不同的进程,由于CPU处理的速度非常快,在用户看来就是连续的画面,所以就实现了多进程。可以想象为很多人要喝水,但饮水机只有一个,所有人排成一队,队首的人接一点水然后排到队尾,第二个人再接一点水,以此类推,最后所有人都可以接上水。
1. 进程概念
进程是操作系统的系统资源分配的基本单位。进程是启动的程序,数据都在内存中,占用CPU和物理内存。
进程与程序的区别为进程是动态的。进程是运行中的程序,程序是储存在硬盘上的可执行代码。
1.1 进程的基本属性
进程的基本属性包括:进程号(PID)、父进程号(PPID)、进程组号(PGID)、真实用户号(UID)、真实组号(GID)、有效用户号(EUID)、有效用户组号(EGID),以及进程所占有的内存区域、打开的文件描述符和进程环境等。
获取进程ID: pid_ t getpid()
获取父进程ID: pid_t getppid()
获取进程的实际用户:pid_t getuid()
获取进程的有效用户:pid_t geteuid()
获取进程的实际组: pid_t getgid()
获取进程的有效组: pid_t getegid()
返回类型为 pid_t ,其实就是 int ,重新定义是为了让代码的可读性更高
重新定义的过程:
typedef __pid_t pid_t
__STD_TYPE __PID_T_TYPE __pid_t
#define __STD_TYPE typedef
#define __PID_T_TYPE __S32_TYPE
#define __S32_TYPE int
1.2 进程结构
进程控制块(pcb):进程控制块中储存这一个进程的很多信息,主要有:进程id,进程状态,进程切换时需要保存和恢复的CPU寄存器,虚拟空间的信息,控制终端的信息,当前的工作目录,文件描述符表,会话和进程组。
linux进程用户区主要由3部分组成:代码段、数据段和堆栈段
2 进程状态
·(R runnable) 运行状态:正在运行或正在等待运行
· (S sleeping)可中断等待状态:进程正在等待某个时间完成。等待过程中可以被信号或定时器唤醒
· (D uninterrruptible sleep)不可中断等待状态:进程正在等待某个时间完成,在等待中不可以被信号或定时器唤醒,必须等再知道等待的事件发生
· (Z zomble)僵死状态: 进程已终止,但进程描述符依然存在,知道父进程调用 wait() 后释放
· (T traced or stopped)停止状态:进程因为收到 SIGSTOP、SIGSTP、SIGTIN、SIGTOU 信号后停止运行或者该进程正在被跟踪(调试程序是,进城处于被跟踪状态)
3. 进程控制操作
3.1 创建进程
1.fork() 创建新进程,linux下所有的进程都是由进程 init 创建的。执行成功时,在父进程中返回子进程的 PID,类型为 pid_t,在子进程中返回0。执行失败返回 -1。fork函数和一般的函数不同,有两个返回值,在父子进程中的返回值不同,这样以区分父子进程。
2.vfork,vfork() 创建的子进程与父进程共享代码以及数据段。
孤儿进程:一个子进程的父进程先结束,该子进程就成为一个孤儿进程,它由init收养。
僵尸进程:子进程结束,父进程还存在,父进程不释放pcb,子进程就成为僵尸进程。僵尸进程是一个死进程,也就是已经运行结束。父进程结束,释放子进程的pcb,僵尸进程就能被杀死。
3.刚 fork 出来之后,父子进程的两个地址空间用户区数据完全相同,后续各自进行不同的操作。
各个进程的地址空间中的数据是完全独立的。
进行读操作时,映射到物理内存共享
进行写操作时,物理内存中复制一份
总结:fork 出来后,后续父子进程进行的操作,互不影响。读时共享,写时复制。
3.2 exec函数族
1.让父子进程执行不相同的操作
2.等够替换进程地址空间中的源代码.text段。
3.执行另一个程序不需要创建额外的地址空间,使用子进程的地址空间,在当前程序中调用另一个应用程序
exec之前需要 fork(),创建子进程
这里介绍函数族中两的两个函数:
①execl:执行指定目录下的程序,通常执行自定义的应用程序
int execl(const char *path, const char *arg,…);
第一个 arg :占位
后边的 arg :命令的参数
参数写完之后:NULL
举例:execl("/home/huloves/my_ls", “my_ls”, “-l”);
②execlp:执行PATH环境变量能够搜索到的程序,执行系统自带的应用程序,/bin
int execlp(const char *file, const char *arg, …);
file:执行的命令名字,如 “ls”
举例:execlp(“ls”,“ls”, “-l”);
返回-1调用失败,exec函数族不需要返回值,如果函数执行成功不返回,执行失败就打印错误输出并退出程序
4. 进程回收
1.wait(阻塞函数):pid_t wait(int *wstatus);
①参数status:判断子进程是如何结束的,正常退出或被某信号杀死。
②返回值:
- -1:回收失败,说明没有子进程了
- 回收子进程的pid
- 0:等待进程组ID等于调用进程ID的任何子进程。
③注:调用一次该函数只能回收一个子进程
子进程退出的状态–传出参数
1.WIFEXITED(status):为非0,进程正常退出
WEXITSTATUS(status):如果上宏为真,使用此宏,获得进程退出状态(exit/return)的参数
2.WIFSIGNALED(status):为非0,进程异常终止
WTERMSIG(status):如果上宏为真,使用此宏,获取石金成终止的那个信号的编号
pid_t wpid;
while((wpid = wait(&status)) != -1);/
if(wpid == 0){
continue;
}
printf("died pid = %d\n", wpid);
if(WIFEXITED(status)){
printf("return %d\n", WEXITSTATUS(status));
}
else if(WIFSIGNALED(status)){
printf("signed%d\n", WTERMSIG(status));
}
}
2.pid_t waitpid(pid_t pid, int *wstatus, int options);
函数作用:同wait函数
参数:
①pid:
- pid == -1,等待任意子进程。与wait相同
- pid > 0,等待进程ID与pid相等的子进程
- id == 0,等待其组ID等于调用进程的组id的任意进程(回收当前组所有的子进程)
- pid < 0(子进程的pid取反),等待其组ID等于pid的绝对值的任意子进程
②status:子进程的退出状态,用法与wait相同
③option:设置为WNOHANG,waitpid非阻塞;设置为0,waitpid阻塞
返回值:
- 大于0:返回清理掉的子进程ID
- -1:无子进程,回收失败
- ==0:非阻塞,参3为WNOHANG,子进程正在运行