【Linux】进程详解一:进程概念
前言
在上一讲 认识冯诺依曼体系&&初识LinuxOS 中已经学到了计算机的整体的体系结构和操作系统在计算机中起到的重要作用,今天我们就来学习一下,在如此复杂的计算机体系中,操作系统是如何有效的组织和管理计算机的?
一、操作系统如何进行有效管理?
在讲操作系统如何是管理的之前,可以先说一说什么叫做管理?
我们在学校中常说辅导员管理整个年级,那么辅导员管理的整个年级到底指的是啥?**管理其实本质上是在管理一些有效的数据。**辅导员是在管理整个年级中反馈给辅导员的数据信息,而每一个班级反馈的数据信息形成了整个年级的数据信息,每一个班级中的每一个学生反馈的数据信息形成的数据信息组成了整个班级的信息。在辅导员收到这些数据信息的时候,就会进行一些措施去执行。这样就形成了整个管理的过程。
那说了这么多,我们再回来说说操作系统是如何进行管理的。同样操作系统也是通过收到来自计算机中各种数据来进行管理的。那么这些数据从哪里来的呢?这就是操作系统中最重要的概念了,即抽象出各种概念,通过结构体的形式描述出来,最后通过数据结构的形式组织起来。
数据的来源就是操作系统中的众多结构体,为了描述出结构体,操作系统给结构体定义了很多的属性,这样结构体就可以返回很多操作系统所需要的有效信息了。如此多的结构体返回有效的数据信息的时候操作系统还是很头疼,所以此时数据结构就起到了至关重要的作用,即将这些结构体通过不同的数据结构组织起来,通过使用不同的数据结构组织起来的数据所实现的效果是不一样的,可以通过具体的要求来进行选择,如如果想要快速找到元素,可以使用树这种结构。如果想要进行快速地插入元素可以使用链表的形式…
有六个字总结操作系统是如何进行管理的:先描述,再组织。
二、进程管理-初识进程和PCB
1.基本概念
操作系统中有很多抽象出的概念,而进程就是其中最深刻的概念之一。
进程的定义:一个执行中程序的实例,即一个正在执行的程序。
如果站在内核的角度来看:进程是分配系统资源的单位。
2.描述进程-进程控制块(PCB)
前面说了一个抽象的概念需要一个具体的结构体来进行描述的。进程中的信息就被放在了一个叫做进程控制块(PCB)的结构体中。
在不同的操作系统下进程控制块的名称不同(就好像不同地方的人称呼某一个东西会有不同的叫法一样),在Linux操作系统中PCB的具体名称是:task_struct
。
当一个程序被加载到内存中要开始执行的时候,操作系统同时会给该进程分配一个PCB,在Linux中就是task_struct
这里面包含了所有关于进程的数据信息。所以CPU对task_struct
进行管理就相当于对进程进行管理。
2.1 task_struct
task_struct是Linux内核的一种数据结构,它会被装载到RAM里并包含进程的信息。每个进程都把它的信息放在task_struct这个数据结构里面,而task_struct包含以下内容:
标识符:与进程相关的唯一标识符,用来区别其他进程
状态:进程会有不同的状态,如运行,停止等等
优先级:相对于其他进程的优先顺序
程序计数器:程序中即将执行的下一条指令的地址
内存指针:包括程序代码和进程相关数据的是很
上下文信息:进程执行时CPU的寄存器中的数据
IO状态信息: 包括显示的I/O请求,分配给进程的I/O设备和正在被进程使用的文件列表。
记账信息:可能包括处理器时间总和,使用的时钟总数,时间限制,记账号等
1.进程标示符: 描述本进程的唯一标示符,用来区别其他进程。
也就是进程的PID,PID是操作系统中唯一标识的进程号。
有两个获得进程PID的方式:
1.1.可以使用ps aux
查看进程的信息。
1.2.可以使用系统接口得到进程PID和父进程的PPID
#include <stdio.h>
#include <unistd.h>
int main() {
printf("pid=%d, ppid=%d\n", getpid(), getppid()); // 进程号和父进程号
return 0;
}
2.进程状态
后面会详细讲解每一个状态,这里先大概地介绍一下。
3.优先级
因为CPU资源有限,而进程却有很多个,所以需要优先级这个属性去决定了进程拿到资源的顺序。****
4. 程序计数器: 程序中即将被执行的下一条指令的地址。
CPU有三个工作:取指令,分析指令和执行指令。CPU中的指令寄存器每一次都会保存下一条指令的地址,以此来进行指令判断。
5. 内存指针: 包括程序代码和进程相关数据的指针,还有和其他进程共享的内存块的指针。
6.上下文数据
通常操作系统内核使用一种叫做**「上下文切换」**的方式来实现控制流。
实行这种机制是因为CPU只有一套寄存器,所有只能有将一个进程的存储数据放入寄存器中计算,从而形成了上下文数据。但是同时有多个进程的时候,操作系统为了使得CPU的利用率最高,所以会让进程之间来回的切换,一般进程切换有两种情况。
6.1 第一种情况:我们称两个执行流在执行的时间上与另一个执行流有重叠的部分,就称这个两个执行流在 「并发的运行」。一个进程在和其他的进程轮流轮流运行成为 「多任务」。一个进程执行它的控制流的那一段时间叫做 「时间片」。简单来说,每一个进程都会有最多执行的时间限制,如果执行时间超过了时间片,就会自动的退出执行。
6.2 第二种情况:当操作系统内核,发现一个优先级更高的进程的时候,该优先级更高的进程就会「抢占」当前进程的位置,然后执行优先级更高的进程。等到该进程执行完后,在执行被 「抢占」 的进程。这种决策方式叫做 「调度」。
以上两种情况,都会使得进程莫名其妙的退出CPU的执行,但是下次CPU还想接着上一次执行的地方继续执行那个莫名其妙退出的进程,所以就需要在进程退出之前,在task_struct
中保留下上一次执行的数据,方便下一次再被执行。
7. I/ O状态信息: 包括显示的I/O请求,分配给进程的I/ O设备和被进程使用的文件列表。
3.组织进程
可以在内核源代码里找到task_struct
。所有运行在系统里的进程都以task_struct
双链表链表的形式存在内核里。如果在情况复杂的情况下,双链表中的节点也有可能存在在其他的数据结构中,例如队列等。
4.查看进程
查看进程有三种方式:
4.1.通过系统目录
第一种方式:在/proc
这个目录下保存着所有进程的信息
4.2.通过ps命令
第二种方式:可以使用命令
ps aux # 查看系统中所有的进程信息
ps axj # 可以查看进程的父进程号
第三种方式:使用top
命令
top # 动态的查看进程的信息,其中的信息默认3秒回更新
5.创建进程-fork()
创建进程有两种创建方式:
1.使用./
运行某一个可执行程序,这种是最常见的方式
2.使用系统调用接口创建进程,即使用fork()
当时用fork()
函数之后,就在原来的进程中创建了一个子进程,在fork()
之前的代码只被父进程执行,在fork()
之后的代码有父子进程一起执行。
创建的子进程和父进程几乎一模一样,子进程和父进程的共享地址空间,子进程可以或者父进程中所有的文件,只有PID是父子进程最大的不同。
利用fork创建一个进程来举个例子:
#include <stdio.h>
#include <unistd.h>
int main()
{
pid_t pid = fork();
if (pid < 0) {
printf("error");
}
if (pid == 0) {
printf("i am a child process\n"); // 输出
} else {
printf("i am a father process\n"); // 输出
}
return 0;
}
如果fork成功创建了一个进程,那么上面的代码就会输出i am a child process i am a father process
这两句话。
这里面有很多有意思的点:
-
fork函数调用一次,返回两次。
上面的代码是如何实现执行两个不同的分支语句的呢?其实是因为fork函数会返回两个返回值,一个是子进程会返回0,一个是父进程会返回子进程的PID。所以会同时进程两个分支语句中。
-
并发执行
父子进程是两个并发运行的独立程序。前面说过并发,就是两个执行流在执行的时间上有重叠的部分。也就是说父子进程谁先被调度是不能确定的。
-
相同但是独立的地址空间
两个进程其实地址空间是一样的,但是它们都有自己私有的地址空间,所以父子进程的运行都是独立的,一个进程中的内存不会影响另一个进程中的内存。
-
共享文件
子进程继承了父进程所有打开的文件,所以父进程调用fork的时候,
stdout
文件呢是打开的,所以子进程中执行的内容也可以输出到屏幕上
下面这个例子有一些的复杂,你可以检验一下自己有没有真正理解fork()
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
int main()
{
fork();
fork();
printf("hello\n");
exit(0);
return 0;
}
上面应该是输出了4个hello
,下面这张图就可以解释
三、进程状态
1. 查看进程的状态
可以使用ps aux
或者ps axj
命令查看进程的状态。
2.不同的进程状态
进程有很多的不同的状态,在kernel源代码中是这样定义的
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运行状态