0%

Linux 并发与进程管理

进程和程序的区别

程序

程序本质上是一系列二进制信息,这些信息描述了如何在运行时创建一个进程:

  • 二进制格式标识:每个程序文件都包含用于描述可执行文件格式的元信息。内核利用此信息来解释 文件中的其他信息。
  • 机器语言指令。
  • 程序入口地址:标识程序开始执行时的起始指令位置。
  • 数据:程序文件包含的变量初始值和程序使用的字面量值(比如字符串)。
  • 符号表及重定位表:描述程序中函数和变量的位置及名称。这些表格有多重用途,其中包括调试和 运行时的符号解析(动态链接)。
  • 共享库和动态链接信息:程序文件所包含的一些字段,列出了程序运行时需要使用的共享库,以及 加载共享库的动态连接器的路径名。
  • 其他信息:程序文件还包含许多其他信息,用以描述如何创建进程。

进程

  • 进程是正在运行的程序的实例。是一个具有独立功能的程序,包含运行时所需的各种资源。
  • 它是操作系统动态执行的基本单元,在传统的操作系统中,进程既是基本的分配单元,也是基本的 执行单元。
  • 可以用一个程序来创建多个进程,进程是由内核定义的抽象实体,并为该实体分配用以执行程序的 各项系统资源。
  • 从内核的角度看,进程由用户内存空间和一系列内核数据结构组成,其中用户内存空间包含了程序 代码及代码所使用的变量,而内核数据结构则用于维护进程状态信息。记录在内核数据结构中的信 息包括许多与进程相关的标识号(IDs)虚拟内存表打开文件的描述符表信号传递及处理的 有关信息进程资源使用及限制当前工作目录和大量的其他信息。
  • 对于一个单 CPU 系统来说,程序同时处于运行状态只是一种宏观上的概念,他们虽然都已经开始 运行,但就微观而言,任意时刻,CPU 上运行的程序只有一个。
  • 在多道程序设计模型中,多个进程轮流使用 CPU。而当下常见 CPU 为纳秒级,1秒可以执行大约 10 亿条指令。由于人眼的反应速度是毫秒级,所以看似同时在运行。

单道和多道程序设计

  • 单道程序,即在计算机内存中只允许一个的程序运行。
  • 多道程序设计技术是在计算机内存中同时存放几道相互独立的程序,使它们在管理程序控制下,相 互穿插运行,两个或两个以上程序在计算机系统中同处于开始到结束之间的状态, 这些程序共享计 算机系统资源。引入多道程序设计技术的根本目的是为了提高 CPU 的利用率。
  • 对于一个单 CPU 系统来说,程序同时处于运行状态只是一种宏观上的概念,他们虽然都已经开始 运行,但就微观而言,任意时刻,CPU 上运行的程序只有一个。
  • 在多道程序设计模型中,多个进程轮流使用 CPU。而当下常见 CPU 为纳秒级,1秒可以执行大约 10 亿条指令。由于人眼的反应速度是毫秒级,所以看似同时在运行。

并行和并发

并行

并行(parallel): 指在同一时刻,有多条指令在多个处理器上同时执行。

并行

并发

并发(concurrency): 指在同一时刻只能有一条指令执行,但多个进程指令被快速的轮换执行,使 得在宏观上具有多个进程同时执行的效果,但在微观上并不是同时执行的,只是把时间分成若干 段,使多个进程快速交替的执行。

并发

进程控制块(PCB)

为了管理进程,内核必须对每个进程所做的事情进行清楚的描述。内核为每个进程分配一个 PCB(Processing Control Block)进程控制块,维护进程相关的信息,Linux 内核的进程控制块是 task_struct 结构体。

Linux中的PCB包括以下信息:

  • 进程id:系统中每个进程有唯一的 id,用 pid_t 类型表示,其实就是一个非负整数
  • 进程的状态:有就绪、运行、挂起、停止等状态
  • 进程切换时需要保存和恢复的一些CPU寄存器
  • 描述虚拟地址空间的信息
  • 描述控制终端的信息
  • 当前工作目录(Current Working Directory)
  • umask 掩码(创建文件或目录的默认权限)
  • 文件描述符表,包含很多指向 file 结构体的指针
  • 和信号相关的信息
  • 用户 id 和组 id
  • 会话(Session)和进程组
  • 进程可以使用的资源上限(Resource Limit)

5 进程的状态

在三态模型 中,进程状态分为三个基本状态,即就绪态,运行态,阻塞态。在五态模型中,进程分为新建态、就绪 态,运行态,阻塞态,终止态。

  • 运行态:进程占有处理器正在运行
  • 就绪态:进程具备运行条件,等待系统分配处理器以便运行。当进程已分配到除CPU以外的所有必 要资源后,只要再获得CPU,便可立即执行。在一个系统中处于就绪状态的进程可能有多个,通常 将它们排成一个队列,称为就绪队列
  • 阻塞态:又称为等待(wait)态或睡眠(sleep)态,指进程不具备运行条件,正在等待某个事件的完成
三态模型
  • 新建态:进程刚被创建时的状态,尚未进入就绪队列
  • 终止态:进程完成任务到达正常结束点,或出现无法克服的错误而异常终止,或被操作系统及有终 止权的进程所终止时所处的状态。进入终止态的进程以后不再执行,但依然保留在操作系统中等待 善后。一旦其他进程完成了对终止态进程的信息抽取之后,操作系统将删除该进程。

进程相关指令

查看进程

1
2
3
4
5
ps aux / ajx 
a:显示终端上的所有进程,包括其他用户的进程
u:显示进程的详细信息
x:显示没有控制终端的进程
j:列出与作业控制相关的信息

STAT参数意义:

1
2
3
4
5
6
7
8
9
10
11
D 不可中断 Uninterruptible(usually IO)
R 正在运行,或在队列中的进程
S 处于休眠状态
T 停止或被追踪
Z 僵尸进程
W 进入内存交换(从内核2.6开始无效)
X 死掉的进程
< 高优先级
N 低优先级
s 包含子进程
+ 位于前台的进程组

实时进程动态

1
top

可以在使用 top 命令时加上 -d 来指定显示信息更新的时间间隔,在 top 命令执行后,可以按以下按键 对显示的结果进行排序:

  • M 根据内存使用量排序
  • P 根据 CPU 占有率排序
  • T 根据进程运行时间长短排序
  • U 根据用户名来筛选进程
  • K 输入指定的 PID 杀死进程

杀死进程

kill并不是去杀死一个进程,而是给进程发送某个信号

1
kill [-signal] pid
1
2
3
4
kill –l 列出所有信号
kill –SIGKILL 进程ID
kill -9 进程ID
killall name 根据进程名杀死进程
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
kill –l

#输入上述密令的执行结果为:
1) SIGHUP 2) SIGINT 3) SIGQUIT 4) SIGILL 5) SIGTRAP
6) SIGABRT 7) SIGBUS 8) SIGFPE 9) SIGKILL 10) SIGUSR1
11) SIGSEGV 12) SIGUSR2 13) SIGPIPE 14) SIGALRM 15) SIGTERM
16) SIGSTKFLT 17) SIGCHLD 18) SIGCONT 19) SIGSTOP 20) SIGTSTP
21) SIGTTIN 22) SIGTTOU 23) SIGURG 24) SIGXCPU 25) SIGXFSZ
26) SIGVTALRM 27) SIGPROF 28) SIGWINCH 29) SIGIO 30) SIGPWR
31) SIGSYS 34) SIGRTMIN 35) SIGRTMIN+1 36) SIGRTMIN+2 37) SIGRTMIN+3
38) SIGRTMIN+4 39) SIGRTMIN+5 40) SIGRTMIN+6 41) SIGRTMIN+7 42) SIGRTMIN+8
43) SIGRTMIN+9 44) SIGRTMIN+10 45) SIGRTMIN+11 46) SIGRTMIN+12 47) SIGRTMIN+13
48) SIGRTMIN+14 49) SIGRTMIN+15 50) SIGRTMAX-14 51) SIGRTMAX-13 52) SIGRTMAX-12
53) SIGRTMAX-11 54) SIGRTMAX-10 55) SIGRTMAX-9 56) SIGRTMAX-8 57) SIGRTMAX-7
58) SIGRTMAX-6 59) SIGRTMAX-5 60) SIGRTMAX-4 61) SIGRTMAX-3 62) SIGRTMAX-2
63) SIGRTMAX-1 64) SIGRTMAX

数字9为SIGKILL

kill –SIGKILL 进程IDkill -9 进程ID等价,也可根据名字来杀死进程:killall name 根据进程名杀死进程

进程号相关函数

每个进程都由进程号来标识,其类型为 pid_t(整型),进程号的范围:0~32767。进程号总是唯一

的,但可以重用。当一个进程终止后,其进程号就可以再次使用。
任何进程(除 init 进程)都是由另一个进程创建,该进程称为被创建进程的父进程,对应的进程号称为

父进程号(PPID)。 进程组是一个或多个进程的集合。他们之间相互关联,进程组可以接收同一终端的各种信号,关联的进

程有一个进程组号(PGID)。默认情况下,当前的进程号会当做当前的进程组号。 进程号和进程组相关函数:

1
2
3
pid_t getpid(void);
pid_t getppid(void);
pid_t getpgid(pid_t pid);
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#include <iostream>
#include <sys/types.h>
#include <unistd.h>

using namespace std;

int main(){
cout<<"进程号"<<getpid()<<endl;
cout<<"父进程号"<<getppid()<<endl;
cout<<"9568进程组号"<<getpgid(9568)<<endl;
return 0;
}

执行结果:
进程号10246
父进程号32628
9568进程组号9568

进程创建

系统允许一个进程创建新进程,新进程即为子进程,子进程还可以创建新的子进程,形成进程树结构模型。除了

1
2
3
4
5
#include <sys/types.h>

#include <unistd.h>

pid_t fork();

返回值:
成功:子进程中返回 0,父进程中返回子进程 ID 失败:返回 -1

失败的两个主要原因:
当前系统的进程数已经达到了系统规定的上限,这时 errno 的值被设置为 EAGAIN 系统内存不足,这时 errno 的值被设置为 ENOMEM

1
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
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
/*
Linux下创建子进程实例
#include <sys/types.h>
#include <unistd.h>

pid_t fork(void);
函数的作用:用于创建子进程。
返回值:
fork()的返回值会返回两次。一次是在父进程中,一次是在子进程中。
在父进程中返回创建的子进程的ID,
在子进程中返回0
如何区分父进程和子进程:通过fork的返回值。
在父进程中返回-1,表示创建子进程失败,并且设置errno

父子进程之间的关系:
区别:
1.fork()函数的返回值不同
父进程中: >0 返回的子进程的ID
子进程中: =0
2.pcb中的一些数据
当前的进程的id pid
当前的进程的父进程的id ppid
信号集

共同点:
某些状态下:子进程刚被创建出来,还没有执行任何的写数据的操作
- 用户区的数据
- 文件描述符表

父子进程对变量是不是共享的?
- 刚开始的时候,是一样的,共享的。如果修改了数据,不共享了。
- 读时共享(子进程被创建,两个进程没有做任何的写的操作),写时拷贝。

*/
#include <sys/types.h>
#include <unistd.h>
#include <stdio.h>

int main() {

int num = 10;

// 创建子进程
pid_t pid = fork();

// 判断是父进程还是子进程
if(pid > 0) {
// printf("pid : %d\n", pid);
// 如果大于0,返回的是创建的子进程的进程号,当前是父进程
printf("i am parent process, pid : %d, ppid : %d\n", getpid(), getppid());

printf("parent num : %d\n", num);
num += 10;
printf("parent num += 10 : %d\n", num);

} else if(pid == 0) {
// 当前是子进程
printf("i am child process, pid : %d, ppid : %d\n", getpid(),getppid());

printf("child num : %d\n", num);
num += 100;
printf("child num += 100 : %d\n", num);
}

// for循环
for(int i = 0; i < 3; i++) {
printf("i : %d , pid : %d\n", i , getpid());
sleep(1);
}

return 0;
}

/*
实际上,更准确来说,Linux 的 fork() 使用是通过写时拷贝 (copy- on-write) 实现。
写时拷贝是一种可以推迟甚至避免拷贝数据的技术。
内核此时并不复制整个进程的地址空间,而是让父子进程共享同一个地址空间。
只用在需要写入的时候才会复制地址空间,从而使各个进行拥有各自的地址空间。
也就是说,资源的复制是在需要写入的时候才会进行,在此之前,只有以只读方式共享。
注意:fork之后父子进程共享文件,
fork产生的子进程与父进程相同的文件文件描述符指向相同的文件表,引用计数增加,共享文件偏移指针。
*/