进程
在学习进程之前,提出一个问题,操作系统是怎么进行进程管理的呢?很简单,就是先去把进程描述起来,再把进程组织起来。
概念:程序的一个执行实例,正在执行的程序,从内核的观点来说,就是担当分配系统资源(CPU时间,内存)的一个实体,
一、描述进程——PCB
进程信息被放在一个叫做进程控制块的结构中,可以理解为进程属性的集合,称之为:PCB,在Linux下,PCB是一个叫做task_struct的结构体,这个结构体里面存放了进程的有关信息。
task_struct结构体的内容分类:
①标识符(PID):描述本进程的唯一的标识符,用来区别其他进程
获取pid的方法有很多,最推荐的一种就是通过系统调用getpid()来获取进程的pid,
②状态:任务状态,退出码,退出信号等
状态分类:R运行状态(runing) S睡眠状态(sleeping) D磁盘休眠状态(Disk sleeping) T停止状态(stopped) X死亡状态(dead) Z僵尸状态(zombie)
③优先级:相对于其他进程的优先级
PRI:进程可执行的优先级,值越小优先级越高
NI:代表nice值,表示进程可被执行的优先级的修正数值
④程序计数器:程序中即将被执行的下一条指令的地址
⑤内存指针:包括程序代码和进程相关数据的指针,还有和其他进程共享的内存块的指针
⑥上下文数据:进程执行时处理器的寄存器中的数据
⑦I/O状态信息:包括显示的I/O请求,分配给进程的I/O设备和被进程使用的文件列表
⑧记账信息:可能包括处理器的时间总和,使用的时钟数总和,时间限制,记账号等
⑨其他信息
task_struct结构里面还存放了很多的有关进程的信息,我们只罗列出比较常用几点信息,如果有兴趣可以在深入学习
二、组织进程
因为进程需要不断的关闭和开启,和数据结构中链表的结构很相似,所以运行在系统的进程都以task_struct链表的形式存在内核里面,此时我们就把进程组织起来了。
三、查看进程
现在进程已经被描述和组织起来,那么有些方法可以让我们查看进程呢?
①进程信息可以被 /proc 文件查看; 比如:要查看PID(进程的标识符)为1的进程,就需要查看 /proc/1 这个文件夹
②通过系统调用获取进程标识符:
#include<stdio.h>
#include<sys/types.h>
#include<unistd.h>
int main(){
printf("pid:%d\n",getpid());
printf("ppid:%d\n",getppid());
return 0;
}
Tip:getppid()函数是获取父进程的标识符,getpid()函数是获取子进程的标识符
四、创建进程
通过系统调用创建进程,也就是使用fork()函数
此时我们先去运行一段代码:
#include<stdio.h>
#include<sys/types.h>
#include<unistd.h>
int main(){
pid_t ret = fork();
printf("hello proc:%d,ret = %d\n",getpid(),ret);
return 0;
}
运行结果:
我们在代码中只有一个printf语句,但是为什么 会有两个输出语句呢?
原因就是:fork在创建进程的时候,有两个返回值,给父进程返回子进程的pid,给子进程返回0,此时父子进程代码共享,数据各自开辟空间,私有一份
所以在父进程中,getpid得到的是:2526(父进程的pid),给父进程返回的ret是子进程的pid,也就是2527,
在子进程中,getpid得到的是:2527(子进程的pid),给子进程返回的ret是0,所以显示ret = 0;所以就显示了两个printf;
Tip:父子进程执行的先后顺序不确定,是取决于编译器自己。
五、进程的状态:
R运行状态(running):表明进程要么在运行中,要么在运行队列里面
S睡眠状态(sleeping):意味着进程在等待事件的完成,这个睡眠也叫做可中断睡眠
D磁盘休眠状态(Disk sleep):这个状态的睡眠通常在等待I/O的结束,也叫做不可中断睡眠
T停止状态(stopped):可以发送SIGSTOP信号给进程,让进程停止。也可以发送SIGCONT信号让进程继续运行。
X死亡状态(dead):这个状态只是一个返回状态,你不会在任务列表里面看到这个状态
Z僵尸状态(zombie):子进程退出,父进程没有读取子进程的退出码,子进程进入Z状态
我们现在代码演示一下僵尸进程:
#include<stdio.h>
#include<sys/types.h>
#include<unistd.h>
#include<stdlib.h>
int main(){
pid_t id = fork();
if(id<0){
perror("fork failed!\n");
return 1;
}
else if (id>0){//这个循环里面,我们让父进程sleep30s
printf("father pid:%d\n",getpid());
sleep(30);
}
else {//这个循环里面,我们让子进程sleep5s,之后退出
printf("child pid:%d\n",getpid());
sleep(5);
exit(EXIT_SUCCESS);
}
return 0;
}
运行这段代码之后,在5s之后子进程退出,父进程还在s状态,那么此时子进程的退出码并没有被父进程去接收,此时子进程会进入Z状态。
我们可以在shell中打开另外一个终端,编写脚本来检测进程的状态
这是我们编写的脚本:
运行脚本之后,就可以在终端屏幕上看到程序进入了僵尸状态:
僵尸进程的危害:
①因为进程的状态必须被维护,退出状态也属于进程的基本信息,所以就保存在PCB中,在Z状态中,子进程不退出,PCB就要一直被维护
②子进程进入僵尸状态之后会一直占用内存,造成内存的资源浪费
③内存的泄漏
在这里我们在提一个概念:
孤儿进程,
上面的僵尸状态的例子是子进程先退出,父进程后退出的僵尸进程,而,孤儿进程是父进程先退出,子进程后退出而造成的僵尸进程,用代码实现的话,就是把上面的代码简单的修改一下,此时就不单独举例了。
孤儿进程出现后,这个进程就会被1号init进程领养
六、进程的优先级
概念:CPU资源分配的先后顺序,就是进程的优先级
首先我们在命令行中查看系统进程,输入:ps -l
UID:代表执行者的身份
PID:进程的标识符
PPID:该进程的父进程的标识符
PRI:进程的优先级,数值越低,优先级越高
NI:代表nice值,优先级的修正数值
Tip:PRI值越小,优先级越高,加上nice值之后,PRI(new) = PRI(old) + nice,如果nice值是负数,那么加上nice之后,优先级就变高。
调整进程优先级,在Linux下就是调整nice值
nice的取值范围在:-20 到19
修改进程优先级的命令: nice 和 renice
①启动进程前的调整
开始执行程序就指定nice值:nice -n -5 ./test
②调整已存在的进程的nice值:
renice -5 -p 5525 //把PID为5525的进程的优先级该为-5
其他概念的:
①竞争性:优先级是为了更合理的竞争相关资源
②独立性 :多进程运行需要独享各种资源,多进程运行期间互不干扰
③并行:多个进程分别在多个CPU下同时进行,这个称之为并行
④并发:多个进程在一个CPU下,采用进程切换的方式,在一段时间内,让多个进程都能够运行,称之为并发,因为切换时间太短,宏观看来像是一起进行
限于编者水平,文章难免有缺漏之处,欢迎指正!
Tip:如需转载,请注明出处