一、什么是进程?
进程是由数据段、程序段以及进程PCB构成的进程实体运行的过程。是系统分配资源以及调用的基本单位。其中进程的PCB是进程的数据结构,是进程最核心的部分,其中包含进程描述信息(pid)、进程控制和管理信息、资源分配清单、处理机相关信息
(代码段,代码正文中所涉及的一切,包括全局变量等,
数据段:包括数据堆和数据栈。动态分配的内存位于数据堆,临时变量位于数据栈。)
(进程标识号!!!)
进程最知名的属性就是进程号(processID,PID)和它父进程号(parent processID,PPID)PID和PPID都是非零的整数。 一个PID唯一标识一个进程。 一个进程创建的另一个新进程称为子进程。相反地,创建子进程的进程称为父进程。所有进程追溯其祖先最终都会落到进程号为1的进程身上,这个进程叫init进程 Init进程是linux内核启动后第一个执行的进程。 Init引导系统,启动守护进程并且运行必要的程序。
二、进程的五态模型
进程由创建产生、运行存活、终止死亡。可以简单地把进程看做一个人的历程。
进程在生命周期内,其状态在不断发生变化,通常有如下五种:
(1)运行态。进程正占用处理机;
(2)就绪态。进程获得除处理机外的一切资源,其正在等待使用处理机。而此类进程通常不止一个,此类进程按照优先级进行排序,我们称之为就绪队列;
(3)阻塞态。进程正在等待某一事件完成或者某一资源可用,即使处理机空闲,该状态的进程也无法使用。
(4)创建态。进程正处于创建过程。
(5)终止态。该状态可能是进程正常结束,也可能是其他原因中断退出。
三、进程的控制(创建、终止、阻塞激活、状态切换)
一般把进程控制进程的程序段称为原语,其具有原子性,不可分割。
1>进程创建
引起进程创建的典型事件有四类:
1. 用户登录。 分时系统中,用户在终端键入登录命令
2. 作业调度。 多批道处理系统,当作业调度程序调度到某些作业时,便将他们装入内存,为他们创建进程,并插入就绪队列中;
3. 提供服务。当运行中的用户程序提出某些请求后,操作系统将专门创建一个进程来提供用户服务
4. 应用请求。 前三者都是系统为用户创建进程,而该情况是用户自己为自己创建进程
。
进程的创建过程如下:
1.申请空白的PCB(若申请失败则创建失败)、为新进程分配一个唯一的进程标识号(可以理解为进程的身份证号);
2.为进程分配资源,为数据段、代码段分派必要的内存空间。若空间不足,则进入阻塞态、等待系统分配资源;
3.初始化PCB。主要是处理机状态信息和控制信息,并设计进程的优先级;
4.若就绪队列能容纳新进程则插入就绪队列。,否则进入阻塞态;
下面是几个创建进程的常用方法:
1. system()用于调用shell(命令解释器),执行一个指定的命令;
2. fork()用于创建一个新的进程,该进程几乎是当前进程的一个完全拷贝;
pid_t fork(void); fork执行成功,向父进程返回子进程的PID,并向子进程返回0,这意味着fork即使只调用一次,也会返回两次
fork创建的新进程是和父进程(除了PID和PPID)一样的副本,但是二者是两个独立的进程
父进程和子进程之间有点区别,子进程没有继承父进程的超时设置(使用alarm调用)、父进程创建的文件锁,或者未决信号。下面看一段代码。(该代码应在linux环境中运行)
#include<stdio.h>
#include<unistd.h>
#include<sys/types.h>
#include<sys/wait.h>
int main()
{
pid_t pid; //pid_t是一个typedef定义类型。 用它来表示进程id类型
pid = fork(); //创建子进程
if (pid < 0) //创建失败
{
printf("Fork Failed");
}
else if (pid == 0) //创建成功,此时在子进程中
{
printf("爸爸!的id:%d\n", getppid());
printf("儿子!的id:%d\n", getpid());
//getpid()返回值,输出当前进程ID,也就是刚被创建的子进程的ID。
}
else //创建成功,此时在父进程中
{
printf("儿子的id:%d\n", wait(NULL)); //清除子进程,并输出刚被创建的子进程ID
printf("爸爸的id:%d\n", getpid()); //输出父进程的ID
printf("爷爷的id:%d\n", getppid()); //输出父进程的父进程ID
}
return 0;
}
其运行结果如下。
应当注意,子进程与父进程是两个独立的进程,二者的输出顺序之间没有严格的顺序关系
{ 你不能预计父进程是在它的子进程之前还是之后运行,它的执行是无序的,是异步的。
fork的异步行为意味着你不应该在子进程中执行依赖与父进程的代码,反之亦然。
fork调用可能失败,原因是系统上已经运行了太多进程,已经超过了允许它执行的最大进程数。 fork执行失败,会向父进程返回-1,而且不创建子进程。
fork过程包括把父进程全部内存映像复制给子进程,这个过程很缓慢。
————————————————
原文链接:https://blog.csdn.net/xiangjai/article/details/119302924 }
3. exec可以在进程中用另外的程序来替换当前运行的进程
fork创建了一个新的进程,产生一个新的PID。
execve用被执行的程序完全替换了调用进程的映像。
2>进程的终止
进程终止的原因一般分为三种:
正常结束:
异常结束(C中的各种BUG原因):时空越界、I/O故障
外界干预:人为强行结束进程、父进程死亡
进程的终止过程(类比古代皇帝下达诛杀的命令)
1.根据PID找到相应的PCB,读出其状态;(找到这个人,及其职位)
2. 若该进程正在运行,立即终止执行,且同时将其子孙进程也全部终止;(若正在当官,立马罢免,且其子孙也要陪葬)
3. 将被终止进程的资源全部归还父进程,或者归还系统;(归还父亲或者国家)
4.将PCB从所在队列中移除。
3>进程的阻塞
进程阻塞原因:
1. 向系统请求共享资源失败
2. 等待某种操作完成
3. 新数据尚未到达
4.等待新任务到达
原语操作为: wait()
(进程一旦调用了wait() ,就立即阻塞自己,由wait()自动分析是否当前进程的某个子进程已经退出,如果让它找到了这样一个已经变成僵尸态的子进程, wait ()就会收集这个子进程的信息,并把它彻底销毁后返回;如果没有找到这样一个子进程, wait ()就会一直阻塞在这里,直到有一个出现为止。
wait()要与fork()配套出现,如果在使用fork()之前调用wait() ,wait()的返回值则为-1,正常情况下wait()的返回值为子进程的PID。
当父进程没有使用wait()函数等待已终止的子进程时﹐子进程就会进入一种无父进程清理自己尸体的状态,此时的子进程就是僵尸进程,不能在内核中清理尸体的情况
————————————————
版权声明:本文为CSDN博主「青山瑶图」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/qq_58950881/article/details/123310120)
4>进程切换
进程切换是指处理机从一个进程的运行转到另一个进程上,其过程如下:
1.保存处理机上下文,
2.更新PCB
3.把进程PCB移入相应的队列
4.选择另一个进程执行,更新其PCB
5.更新内存管理中的数据结构
6.恢复处理机上下文
注意区别进程切换与处理机模式(用户态、内核态)切换
四、进程通信
进程通信是指进程间的信息交换通常有以下三类:
1.共享存储。
用户进程空间一般是独立的,系统调用特别开辟一部分共享空间用于信息交换;
2.消息传递。
当共享空间无法开辟时,便使用发送、接收原语直接进行信息交换。
3.管道通信。
必须提供同步,互斥,确认对方是否存在。管道只能采用半双工通信,即某一时刻只能单向传输。要实现父子进程的通信需定义两个管道。