本文基于CentOS,由冯诺依曼系统,操作系统概念,深入解释进程概念,PCB,进程的创建,进程的状态,僵尸进程,孤儿进程。
目录
一、冯诺依曼体系
我们常见的计算机大部分都遵守冯诺依曼体系。
二、操作系统(Operator System)
任何计算机系统都包含一个基本的程序集合,称为操作系统(OS)。笼统的理解,操作系统包括:
内核(进程管理,内存管理,文件管理,驱动管理)
其他程序(例如函数库,shell程序等等)
设计OS的目的是与硬件交互,管理所有的软硬件资源,为用户程序(应用程序)提供一个良好的执行环境。
是一款进行软硬件综合管理的软件。
计算机管理硬件:先描述再组织
1. 描述起来,用struct结构体
2. 组织起来,用链表或其他高效的数据结构
三、进程基本概念
程序的一个执行实例,正在执行的程序等,
从内核角度:担当分配系统资源(CPU时间,内存)的实体。
四、描述进程-PCB
进程信息被放在一个叫做进程控制块的数据结构中,可以理解为进程属性的集合,称之为PCB(process control block),Linux操作系统下的PCB是: task_struct。是进程实体的一部分,是操作系统中最重要的记录型数据结构。PCB是进程存在的唯一标志,系统能且只能通过PCB对进程进行控制和调度,PCB记录了操作系统所需的、用于描述进程的当前情况以及控制进程运行的全部信息。
1.task_ struct
在Linux中描述进程的结构体叫做task_struct。 task_struct是Linux内核的一种数据结构,它会被装载到RAM(内存)里并且包含着进程的信息。
内容分类:
标示符: 描述本进程的唯一标示符,用来区别其他进程。
状态: 任务状态,退出代码,退出信号等。
优先级: 相对于其他进程的优先级。
程序计数器: 程序中即将被执行的下一条指令的地址。
内存指针: 包括程序代码和进程相关数据的指针,还有和其他进程共享的内存块的指针
上下文数据: 进程执行时处理器的寄存器中的数据。
I/O状态信息: 包括显示的I/O请求,分配给进程的I/O设备和被进程使用的文件列表。
记账信息: 可能包括处理器时间总和,使用的时钟数总和,时间限制,记账号等。
其他信息
五、组织进程
可以在内核源代码里找到它。所有运行在系统里的进程都以task_struct链表的形式存在内核里。
六、查看进程
进程的信息可以通过 /proc 系统文件夹查看
可以使用top和ps这些用户级工具来获取
七、通过系统调用获取进程标示符
进程id(PID) 父进程id(PPID)
#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
int main()
{
printf("pid: %d\n", getpid());
printf("ppid: %d\n", getppid());
return 0;
}
八、通过系统调用创建进程-fork-1
fork有两个返回值
父子进程代码共享,数据各自开辟空间,私有一份(采用写时拷贝)
fork 之后通常要用 if 进行分流:
#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
int main()
{
int ret = fork();
if(ret < 0){
perror("fork");
return 1;
}
else if(ret == 0)
{ //child
printf("I am child : %d!, ret: %d\n", getpid(), ret);
}else
{ //father
printf("I am father : %d!, ret: %d\n", getpid(), ret);
}
sleep(1);
return 0;
}
九、进程状态
1.状态的种类
一个进程可以有几个状态(在 Linux内核里,进程有时候也叫做任务)。
状态在kernel源代码里定义:
/*
* The task state array is a strange "bitmap" of
* reasons to sleep. Thus "running" is zero, and
* you can test for combinations of others with
* simple bit tests.
*/
static const char * const task_state_array[] = {
"R (running)", /* 0 */
"S (sleeping)", /* 1 */
"D (disk sleep)", /* 2 */
"T (stopped)", /* 4 */
"t (tracing stop)", /* 8 */
"X (dead)", /* 16 */
"Z (zombie)", /* 32 */
};
其中:
R运行状态(running): 并不意味着进程一定在运行中,它表明进程要么是在运行中要么在运行队列里。
S睡眠状态(sleeping): 意味着进程在等待事件完成(这里的睡眠有时候也叫做可中断睡眠 (interruptible sleep))。
D磁盘休眠状态(Disk sleep):有时候也叫不可中断睡眠状态(uninterruptible sleep),在这个状态的 进程通常会等待IO的结束。
T停止状态(stopped):可以通过发送 SIGSTOP 信号给进程来停止(T)进程。这个被暂停的进程可 以通过发送 SIGCONT 信号让进程继续运行。
X死亡状态(dead):这个状态只是一个返回状态,你不会在任务列表里看到这个状态。
十、僵尸进程
僵死状态(Zombies)是一个比较特殊的状态。当进程退出并且父进程没有读取到子进程退出的返回代码时就会产生僵死(尸)进程。
僵死进程会以终止状态保持在进程表中,并且会一直在等待父进程读取退出状态代码。 所以,只要子进程退出,父进程还在运行,但父进程没有读取子进程状态,子进程进入Z状态。
例如:
#include <stdio.h>
#include <stdlib.h>
int main()
{
pid_t id = fork();
if(id < 0){
perror("fork");
return 1;
}
else if(id > 0){ //parent
printf("parent[%d] is sleeping...\n", getpid());
sleep(30);
}else{
printf("child[%d] is begin Z...\n", getpid());
sleep(5);
exit(EXIT_SUCCESS);
}
return 0;
}
僵尸进程危害:进程的退出状态必须被维持下去。可父进程如果一直不读取,那子进程就一直处于Z状态。
维护退出状态本身就是要用数据维护,也属于进程基本信息,所以保存task_struct(PCB)中,Z状态一直不退出,PCB一直都要维护。
一个父进程创建了很多子进程就会造成内存资源的浪费。因为数据结构对象本身就要占用内存,想想C中定义一个结构体变量,是要在内存的某个位置进行开辟空间,会造成内存泄漏。
十一、孤儿进程
父进程先退出,子进程就称之为“孤儿进程” ,孤儿进程被1号init进程领养,有init进程回收。
#include <iostream>
#include <sys/types.h>
#include <unistd.h>
#include <stdlib.h>
int main ()
{
int ret = fork();
while(1)
{
if(ret > 0) //说明是父进程
{
printf("father : %d, ret : %d\n", getpid(), ret);
sleep(3);
exit(0);
}
else if(ret == 0) {
printf("child : %d, ret : %d\n", getpid(), ret);
sleep(5);
}
else
{
printf("fork\n");
return 1;
}
}
return 0;
}