进程概念
# 冯诺依曼体系
我们常见的计算机,如笔记本。我们不常见的计算机,如服务器,大部分都遵守冯诺依曼体系:
# 操作系统
操作系统的定位:
简单来说,操作系统就是一款进行软硬件资源管理的软件。
设计操作系统的目的:
- 与硬件进行交互,管理所有的软硬件资源。
- 为用户程序(应用程序)提供一个良好的执行环境。
# 进程概念
- 课本概念:程序的一个执行实例,正在执行的程序等
- 内核观点:担当分配系统资源(CPU时间,内存)的实体
可执行程序本质上是一个文件,是放在磁盘上的。当我们双击这个可执行程序将其运行起来时,本质上是将这个程序加载到内存当中了,因为只有加载到内存后,CPU才能对其进行逐行的语句执行,而一旦将这个程序加载到内存后,我们就不应该将这个程序再叫做程序了,严格意义上将应该将其称之为进程。如果右太多加载进来的程序,此时,操作系统就需要进行管理(先描述,后组织)。
先描述,后组织
# 描述进程-PCB
# task_struct
PCB实际上是对进程控制块的统称,在Linux中描述进程的结构体叫做task_struct。 task_struct是Linux内核的一种数据结构,它会被装载到RAM(内存)里并且包含进程的信息。
进程 = 内核数据结构(task_struct) + 进程对应的磁盘代码
# task_struct内容分类
task_struct就是Linux当中的进程控制块,task_struct当中主要包含以下信息:
- 标示符: 描述本进程的唯一标示符,用来区别其他进程。
- 状态: 任务状态,退出代码,退出信号等。
- 优先级: 相对于其他进程的优先级。
- 程序计数器(pc): 程序中即将被执行的下一条指令的地址。
- 内存指针: 包括程序代码和进程相关数据的指针,还有和其他进程共享的内存块的指针。
- 上下文数据: 进程执行时处理器的寄存器中的数据。
- I/O状态信息: 包括显示的I/O请求,分配给进程的I/O设备和被进程使用的文件列表。
- 记账信息: 可能包括处理器时间总和,使用的时钟总和,时间限制,记账号等。
- 其他信息。
# 组织进程
可以在内核源代码里找到它。所有运行在系统里的进程都以task_struct链表的形式存在内核里。
# 查看进程
# 系统目录查看
进程的信息可以通过bin/proc
系统文件夹查看
比如:要获取PID为2565167的进程信息,你需要查看 /proc/2565167
这个文件夹
# ps命令查看
使用ps aux
可以打印出当前所有进程的信息。
比如:要获取PID为2565167的进程信息,你需要查看进行以下命令:
[cen@VM-8-2-opencloudos proc]$ ps aux | head -1 && ps aux | grep 2565167
USER PID %CPU %MEM VSZ RSS TTY STAT START TIME COMMAND
cen 2565167 0.0 0.1 13856 1808 pts/2 S+ 15:54 0:00 ./test
cen 2567165 0.0 0.0 222012 1096 pts/3 S+ 15:59 0:00 grep --color=auto 2565167
2
3
4
# 获取进程的PID和PPID
进程id(PID) 父进程id(PPID)
#include<iostream>
#include <unistd.h>
using namespace std;
#include<sys/types.h>
int main() {
while(1) {
sleep(20);
cout << "我是一个进程:" << "PID:" << getpid() << " PPID:" << getppid() << endl;
}
return 0;
}
2
3
4
5
6
7
8
9
10
11
12
13
14
我是一个进程:PID:2578545 PPID:2577206
[cen@VM-8-2-opencloudos ~]$ ps aux | head -1 && ps aux | grep test
USER PID %CPU %MEM VSZ RSS TTY STAT START TIME COMMAND
cen 2578545 0.0 0.1 13856 1892 pts/4 S+ 16:30 0:00 ./test
cen 2579086 0.0 0.0 222012 1180 pts/5 S+ 16:31 0:00 grep --color=auto test
[cen@VM-8-2-opencloudos ~]$ ps aux | head -1 && ps aux | grep 2577206
USER PID %CPU %MEM VSZ RSS TTY STAT START TIME COMMAND
cen 2577206 0.0 0.2 226016 5144 pts/4 Ss 16:27 0:00 -bash
cen 2579349 0.0 0.0 222012 1120 pts/5 S+ 16:32 0:00 grep --color=auto 2577206
2
3
4
5
6
7
8
由此,命令行上启动的命令,它的父进程一般都是bash
# 创建进程
# fork创建子进程
**fork函数创建子进程:**fork是一个系统调用级别的函数,其功能就是创建一个子进程。(函数执行前,只有父进程;函数执行后,父进程 + 子进程) 返回值:
- 如果子进程创建成功,在父进程中返回子进程的PID,而在子进程中返回0。
- 如果子进程创建失败,则在父进程中返回 -1。
实际上,使用fork函数创建子进程,在fork函数被调用之前的代码被父进程执行,而fork函数之后的代码,则默认情况下父子进程都可以执行。需要注意的是,父子进程虽然代码共享,但是父子进程的数据各自开辟空间(采用写时拷贝)并且通过返回值的不同,来让父子进程执行不同的代码。
#include<iostream>
#include <unistd.h>
using namespace std;
#include<sys/types.h>
int main() {
fork();
cout << "我是一个进程:" << "PID:" << getpid() << " PPID:" << getppid() << endl;
return 0;
}
2
3
4
5
6
7
8
9
[cen@VM-8-2-opencloudos ~]$ ./test
我是一个进程:PID:2607219 PPID:2577206
我是一个进程:PID:2607220 PPID:2607219
2
3
分析:运行cpp程序,父进程调用fork(),产生一个子进程,fork()调用后,父子两个进程分别执行cout语句,输出各自的PID和PPID。 例如:父进程开始,PID:2607219 PPID:2577206(bash的PID),子进程开始,PID:2607220 PPID:2607219(父进程的PID)。
# if分流
据返回值的不同来让父子进程执行不同的代码,从而做不同的事。
#include<iostream>
#include <unistd.h>
using namespace std;
#include<sys/types.h>
int main() {
pid_t id = fork();
if(id == 0) {
while(1) {
cout << "子进程..." << endl;
sleep(2);
}
}
else if(id > 0) {
while(1) {
cout << "父进程" << endl;
sleep(2);
}
}
else
// fork error
return 0;
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
输出结果:父进程、子进程循环打印。
# 进程状态
Linux操作系统的源代码当中对于进程状态的定义:
/*
* 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 */
};
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# 运行状态-R
一个进程处于运行状态(running),并不意味着进程一定处于运行当中,运行状态表明一个进程要么在运行中,要么在运行队列里。也就是说,可以同时存在多个R状态的进程。
# 浅度睡眠状态-S
一个进程处于浅度睡眠状态(sleeping),意味着该进程正在等待某件事情的完成,处于浅度睡眠状态的进程随时可以被唤醒,也可以被杀掉(这里的睡眠有时候也可叫做可中断睡眠(interruptible sleep))。
# 深度睡眠状态-D
一个进程处于深度睡眠状态(disk sleep),表示该进程不会被杀掉,即便是操作系统也不行,只有该进程自动唤醒才可以恢复。该状态有时候也叫不可中断睡眠状态(uninterruptible sleep),处于这个状态的进程通常会等待IO的结束。
例如,某一进程要求对磁盘进行写入操作,那么在磁盘进行写入期间,该进程就处于深度睡眠状态,是不会被杀掉的,因为该进程需要等待磁盘的回复(是否写入成功)以做出相应的应答。(磁盘休眠状态)
# 僵尸进程
进程在退出的时候,不能立即释放对用的资源,保存一段时间后,让父进程或OS来进行读取,如果没有回收,此时的状态就是僵尸状态。
僵死进程会以终止状态保持在进程表中,并且会一直在等待父进程读取退出状态代码。所以,只要子进程退出,父进程还在运行,但父进程没有读取子进程状态,子进程进入Z状态。
# 孤儿进程
如果父进程先退出,子进程就称之为“孤儿进程”。孤儿进程被1号init进程
领养,退出的时候被init进程回收。
如果一个进程变成孤儿,会自动变成后台进程。
# 进程优先级
# 引入
cpu资源分配的先后顺序,就是指进程的优先权(priority)。优先权高的进程有优先执行权利。配置进程优先权对多任务环境的linux很有用,可以改善系统性能,还可以把进程运行到指定的CPU上,这样一来,把不重要的进程安排到某个CPU,可以大大改善系统整体性能。
# PRI和NI
执行ps -l
来查看系统进程:
F S UID PID PPID C PRI NI ADDR SZ WCHAN TTY TIME CMD
4 S 1001 365554 365551 0 80 0 - 2063 do_wai pts/0 00:00:00 bash
0 R 1001 365795 365554 0 80 0 - 2736 - pts/0 00:00:00 ps
2
3
其中:
- UID : 代表执行者的身份
- PID : 代表这个进程的代号
- PPID :代表这个进程是由哪个进程发展衍生而来的,即父进程的代号
- PRI :代表这个进程可被执行的优先级,值越小越早被执行
- NI :代表这个进程的nice值
理解: NI就是我们所要说的nice值,其表示进程可被执行的优先级的修正数值,PRI值越小越快被执行,那么加入nice值后,将会使得PRI变为:PRI(new) = PRI(old)(80) + nice,所以,调整进程优先级,在Linux下,就是调整进程nice值(PRI(old)默认等于80,nice其取值范围是-20至19,一共40个级别)。
# 查看进程优先级
ps -al
使用ps -al命令查看该进程优先级的信息
top
通过top命令更改进程的nice值,top命令就相当于Windows操作系统中的任务管理器,它能够动态实时的显示系统当中进程的资源占用情况。
具体:按R,输入更改进程的UID,进行更改nice值,最后按Q退出
# 环境变量
环境变量(environment variables)一般是指在操作系统中用来指定操作系统运行环境的一些参数,环境变量通常具有某些特殊用途,还有在系统当中通常具有全局特性。
# 常见环境变量
- PATH : 指定命令的搜索路径
- HOME : 指定用户的主工作目录(即用户登陆到Linux系统中时,默认的目录)
- SHELL : 当前Shell,它的值通常是/bin/bash
- ...
- 查看环境变量方法:
echo $NAME
NAME:你的环境变量名称 - 代码获取环境变量的方法:
cout << getenv("PATH") << endl;
或者通过第三方变量environ获取
extern char **environ; // 一个环境变量指针数组,指向HOME、SHELL等等
int i = 0;
for(; environ[i]; i++){
cout << environ[i] << endl;
}
2
3
4
5
# 环境变量相关命令
- echo: 显示某个环境变量值
- export: 设置一个新的环境变量
- env: 显示所有环境变量
- unset: 清除环境变量
- set: 显示本地定义的shell变量和环境变量
# 进程地址空间
进程地址空间就类似于一把尺子,尺子按照刻度被划分为各个区域(内核空间约1GB,其余部分占3GB)。 例如代码区、堆区、栈区等。而在结构体mm_struct当中,便记录了各个边界刻度,例如代码区的开始刻度与结束刻度,如下图所示:
在结构体mm_struct当中,各个边界刻度之间的每一个刻度都代表一个虚拟地址, 这些虚拟地址通过页表映射与物理内存建立联系。由于虚拟地址是线性增长的,所以虚拟地址又叫做线性地址。
进程地址空间里面不是物理地址,而是虚拟地址(线性地址)。物理地址用户一概是看不到的,是由操作系统统一进行管理的(通过页表映射:进程 -- 映射 --> 物理地址)