书上的概念是,进程是程序的一个执行实例,正在执行的程序等。在内核的观点看,进程是承担分配系统资源(CPU时间,内存)的实体。
OS为了管理进程,将进程信息描述出来,放在一个数据结构中,可以理解为进程属性的集合。这个数据结构称为PCB(process control block),Linux操作系统下的PCB是: task_struct,task_struct是Linux内核的一种数据结构,是一个结构体。它会被装载到RAM(内存)里,task_struct中几乎包含了进程相关的所有属性信息。
操作系统将所有的进程控制块(task_struct)用双链表的形式组织链接起来。OS对进程的管理,转化成为了对进程信息的管理,先描述再组织。在内核里面将对进程的管理变为对双链表的增删查改。
1.标识符:描述本进程的唯一标示符,用来区别其他进程。
Linux中用pid来标识进程,每一个进程在运行起来后都有一个唯一的标识,就是pid。
2.状态:任务状态,退出代码,退出信号等。
3.优先级: 相对于其他进程的优先级。优先级本质是在资源有限的前提下,确立谁先访问资源,谁后访问资源的问题。
4.程序计数器: 程序中即将被执行的下一条指令的地址。
CPU的核心工作流程可以简单理解为:
a.取指令 b.分析指令 c.执行指令
CPU运行的代码都是进程的代码,它怎样知道取进程中的哪些指令?
在cpu中有一个eip寄存器,这个寄存器会保存当前正在执行指令的下一条指令的地址。这个寄存器通常称为PC指针。当进程不运行了,但是它没有跑完的时候,将eip里面的内容保存进pcb里。
5.内存指针:包括程序代码和进程相关数据的指针,还有和其他进程共享的内存块的指针。可以通过内存指针找到该进程相关的代码和数据。
6.上下文数据:进程在CPU上运行,CPU寄存器上会有很多临时数据,当进程运行完毕从cpu上剥离的时候,这些数据是需要被保存的,这些数据称为当前进程的上下文数据。
7.I/O状态信息:包括显示的I/O请求,分配给进程的I/O设备和被进程使用的文件列表。
8.记账信息:包括处理器时间总和,使用的时钟数总和,时间限制,记账号等。
在linux中使用fork函数创建子进程。
pid_t是ProcessID_Type的缩写,其实是宏定义的unsigned int类型。
fork返回值如果子进程创建成功给子进程返回0,父进程返回子进程pid,如果失败了给父进程返回-1。
从程序员的角度看fork之后父子进程共享用户代码,而用户数据各自私有一份。因为代码是只读的,不可以修改或写入,本质上是为了维护进程的独立性。
从操作系统角度看多了一个进程,多了一个内核数据结构task_struct,创建子进程,通常以父进程为模板。其中子进程默认使用父进程的代码和数据(写时拷贝)。
#include
#include
int main()
{
printf("i am father: %d\n", getpid());
pid_t ret = fork();
if(ret == 0)
{
//child
while(1)
{
printf("i am child, pid:%d, ppid:%d\n", getpid(), getppid());
sleep(1);
}
}
else if(ret > 0)
{
//father
while(1)
{
printf("i am father, pid:%d, ppid:%d\n", getpid(), getppid());
sleep(1);
}
}
else
{
//出错
}
return 0;
}
可以通过getpid()函数查看进程pid,getppid()查看该进程父进程的pid。
/*
* 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的结束。
S状态和D状态对比:
S状态称为休眠状态,这种状态是浅度休眠,大部分的情况都属于浅度休眠。所谓浅度休眠指的是这种状态是可以被唤醒的。它虽然是一种休眠状态,但是随时可以接收外部的信号,处理外部的请求(通知它处理,它会在一瞬间醒来处理请求),换句话说,浅度休眠可以对外部事件做出反应。
D状态是深度休眠,深度休眠的进程不可被杀掉,即便是操作系统也不行。D状态通常在访问磁盘这样的IO设备,进行数据拷贝的关键步骤上需要将进程设置为D状态。D状态什么时候结束呢?只能等D状态的进程自动醒来,或者是关机重启(甚至重启都有可能导致机器被夯住)。
不管是深度休眠还是浅度休眠本质上都是一种等待状态。
T停止状态(stopped): 可以通过发送 SIGSTOP 信号给进程来停止(T)进程。这个被暂停的进程可以通过发送 SIGCONT 信号让进程继续运行。
t (tracing stop):进程在gdb调试的时候可能看到这个状态。
进程退出的时候,会自动将自己退出时的相关信息写入进程的PCB中,供OS或者父进程读取。读取成功之后,该进程才算真正死亡(X状态)。当进程退出并且父进程没有读取到子进程退出的返回代码时就会产生僵尸进程(Z状态)。
僵死进程会以终止状态保持在进程表中,并且会一直在等待父进程读取退出状态代码。所以,只要子进程退出,父进程还在运行,但父进程没有读取子进程状态,子进程就进入Z状态。
D状态和Z状态发送9号信号kill不掉,因为一个是在深度休眠,一个已经挂掉了。
僵尸进程的危害
维护退出状态本身就是要用数据维护,也属于进程基本信息,所以保存在task_struct(PCB)中,换句话说,Z状态一直不退出,PCB就要一直维护。
一个父进程创建了很多子进程,就是不回收,就会造成内存资源的浪费,导致内存泄露。
#include
#include
#include
int main()
{
if(fork() > 0)
{
sleep(5);
printf("i am father,pid:%d i quit!\n", getpid());
exit(0);
}
while(1)
{
printf("i am child, pid:%d, ppid:%d\n", getpid(), getppid());
sleep(1);
}
return 0;
}
父进程sleep5秒然后退出,子进程一直打印,通过监控脚本查看父子进程状态信息。
如果一个进程还在运行,而父进程退出了,此时该进程就被称为孤儿进程,被1号进程领养。1号进程被称为systemd,也就是操作系统。
优先级是指进程得到某种资源的先后顺序。
PRI即进程的优先级,或者通俗点说就是程序被CPU执行的先后顺序,此值越小进程的优先级别越高。
NI就是nice值,表示进程可被执行的优先级的修正数值。nice其取值范围是-20至19,一共40个级别。
加入nice值后,将会使得PRI变为:PRI(new)=PRI(old)+nice
PRI(old)永远是80。
Linux进程优先级由pri和nice值共同确定。在linux中优先级的数值越小,优先级越高,优先级的数值越大,优先级越低。
用top命令更改已存在进程的nice:
1.top
2.进入top后按“r”–>输入进程PID–>输入nice值