目录
一.进程概念
1.基本概念
- 课本概念:程序的一个执行实例,正在执行的程序等
- 内核观点:担当分配系统资源(CPU时间,内存)的实体。
总的来说就是 你编写的程序通过编译后形成可执行文件,当你运行可执行文件加载到内存,叫做加载了一个进程。
2.进程描述——PCB
- 进程的信息被放在一个叫做进程控制块的数据结构中,可以理解为进程属性的集合。将这个集合一般化后称之为PCB(process control block),在Linux下具体的PCB是:task_struct
可执行程序加载到内存,是运行了一个进程。实际的大小要比文件本身要大。操做系统管理进程要先对进程进行描述,会添加一些属性(所以比本身文件大),属性包括描述信息+内容代码数据等。
而操作系统允许多个进程同时允许,为了方便管理,操作西永还会将进程组织起来,一般是组织成一个双向链表的数据结构。
2.1 task_strcut和PCB的区别
- PCB是操作系统描述进程的一个统称。
- task_struct是Linux下描述进程的结构体,是Linux内核的一种数据结构,它会被装载到内存里并且包含进程的信息。
总的来说task_struct是PCB的一种,是Linux操作系统下保存进程信息的。不同的操作系统保存信息的结构可能不同。
2.2 task_struct内容分类
- 标识符:描述改进程的唯一标识符,用来区别其它进程。pid
- 状态:任务状态,退出代码,退出信号等等
- 优先级:相对于其它进程的优先级
- 程序计数器:程序中机将被执行的下一条指令的地址
- 内存指针:包括程序代码和进程相关数据的指针,还有和其它进程共享内存块的指针
- 上下文数据:进程执行时处理器的寄存器中的数据
- I/O状态信息:包括显示的I/O请求,分配给进程的I/O设备和被进程使用的文件列表。
- 记账信息:可能包括处理器的时间总和,使用的时钟数的总和,时间限制,记帐号。
- 其它信息
进程是可执行程序与管理进程需要的数据结构的集合。
2.3 组织进程
task_struct是用来描述进程的,操作系统允许多进程一起工作,系统是将这些一个个的进程对象组织成什么样呢?
运行在Linux系统里的进程都是以双向链表的形式组织起来的,方便系统管理。
task_struct是一种自定义类型,也要定义对象,定义的对象也要在内存开辟空间,再将他们组织起来,这些动作都是操作系统做的。
2.4查看进程
所有的进程信息可以通过 ls /proc指令来查看
如果想查看单独某个进程的信息 可以通过下面的指令来查看
当我运行一个程序,就是运行一个进程,程序如下:
1 #include<iostream>
2 #include<unistd.h>
3 using namespace std;
4
5 int main(){
6 while(1){
7 cout<<"hello linux"<<endl;
8 sleep(1);
9 }
10 return 0;
11 }
如果想看到抬头标识
如果指向看到运行的经常,可以在后面加一个grep -v “关键字”命令,不显示包含关键字的信息。
2.5 通过系统调用获取进程标识符
- 获取进程id(PID),getpid()系统调用
- 获取父进程id(PPID),getppid()系统调用
父进程可以理解为进程的在父进程产生的当前进程。
编写代码来获取父子进程id,头文件#include<unistd.h>
1 #include<stdio.h>
2 #include<unistd.h>
3
4 int main(){
5 printf("PID:%d PPID:%d\n",getpid(),getppid());
6 return 0;
7 }
运行后:
当我改一下程序一直运行这个进程时
1 #include<stdio.h>
2 #include<unistd.h>
3
4 int main(){
5 while(1){
6 printf("PID:%d PPID:%d\n",getpid(),getppid());
7 }
8 return 0;
9 }
这里有一个结论就是:运行一个进程的标识符是临时的,在执行标识符会不一样。
我们在来看一下PID为32235的进程是什么?
我们发现PID为32235进程是我们的bash进程。
bash(命令行解释器)运行原理:
bash也是一个进程,是linux的命令行解释器,它不能轻易的挂掉。当一个命令过来时,一般bash会创建一个子进程,来完成对应的任务,运行可执行程序。但是我们也不能认为bash不做一点事,它也有自己的任务。所以我们发现大多数可执行程序的父进程都是bash。
二.创建进程——fork(初识)
1.如何理解进程的创建
创建进程是系统多了一个进程,系统就需要多一组管理进程的数据结构和改进程对应的代码和数据
2.如何创建进程
使用fork系统调用 "pid_t fork(void)"
编写一个程序:
1 #include<stdio.h>
2 #include<unistd.h>
3
4 int main(){
5 int ret= fork();
6 printf("hello proc:%d,ret:%d\n",getpid(),ret);
7 sleep(1);
8 return 0;
9 }
运行后:
运行后我们发现代码中的"printf("hello proc:%d,ret:%d\n",getpid(),ret)"执行了两次。并且两次进程的PID不同,fork返回值也不同。
结论:fork有两个返回值,给父进程(调用fork的进程)返回子进程(创建的进程)的PID,给子进程返回0。如果进程申请失败返回-1。
所以fork后面一般会要用if进行分流,让父子进程处理不同的事情。这就是多进程编写。
1 #include<stdio.h>
2 #include<unistd.h>
3
4 int main(){
5 int ret= fork();
6 if(ret<0){
7 //将上一个函数失败原因输出到stderr,里面的字符串先打印
8 perror("fork");
9 return 1;
10 }
11 else if(ret==0){//子进程
12 printf("i am child:%d!,ret:%d\n",getpid(),ret);
13 sleep(1);
14 }
15 else{ //父进程
16 printf("i am father:%d,ret:%d\n",getpid(),ret);
17 sleep(1);
18 }
19 return 0;
20 }
2.1 为什么fork会有两个返回值?
fork只一个函数,里面含有创建进程的代码,例如:
pid_t fork{
//创建进程代码
//最后return
return pid;
}
在返回前已经将子进程创建好了,子进程也要自行return这条语句。原来进程父进程本来就要执行return语句。所以会有两个返回值。
2.2 fork父子进程的执行顺序和代码数据的复制问题?
直接说结论:父进程创建子进程时,代码是共享的,数据是独立的(写时拷贝)。这个意思就是:父子进程代码是一样的,数据时不一样的,各自私有,所以上面代码的返回值会不一样,但是代码一样。进程的独立性也可以通过数据私有来体现。
运行顺序时不确定的,有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 */
};
- R运行状态(running):并不意味着进程正处于运行中,它表明进程要么时在运行中,要么在cpu的运行队列里。
- S睡眠状态(sleeping):意味着进程在等待某一时间的完成,可以立即被唤醒。
看一条代码:
1 #include<stdio.h>
2 #include<unistd.h>
3
4 int main(){
5 while(1){
6 printf("hello proc!\n");
7 sleep(1);
8 }
9 return 0;
10
11 }
运行后,查看它的状态,也可以使用之前的命令。
- D磁盘休眠状态(Disk sleep):深度睡眠状态,这个进程一般会等待I/O的结束 。
如果一个进程出于S状态,当这个进程往硬盘读取数据时,硬盘是外设很慢,导致进程需要等待,但是如果操作系统发现进程态度时,运行不过来了,就需要杀死一些进程,如果将上述进程杀死了,导致硬盘查找的数据无法处理。如果将该进程状态设置为D状态,OS就无法杀死这个进程了。
- T停止状态(Stopped):可以发送SIGSTOP信号停止进程。可以发送SIGCONT来让进程继续运行。
对于之前代码的进程:
- t追踪停止状态
- X死亡状态(dead):这个状态时返回状态,你不会在任务列表里看到这个状态。
查看进程状态还有一条命令:ps axj 可以查看状态的父子进程
3.1 Z僵尸状态
概念:僵尸状态是一个比较特殊的状态。当进程退出并且父进程没有读取(用wait系统调用读取)到子进程退出的返回代码时就会产生僵尸状态。意思就是子进程退出,父进程还在运行,但是父进程没有读取到子进程状态,子进程就进入了僵尸状态。
特征:子进程的PCB(task_struct)会白保留,子进程的退出信息会保存在PCB中。
僵尸状态会一直以Z状态保持在进程表中,并且会一直等待父进程读取退出状态代码。
为什么要有僵尸状态:
僵尸状态是为了保持子进程的退出信息,方便父进程读取来获得退出的原因(1.是否正常运行完,2.是否异常 3.发生了什么异常)。方便调查。读取退出原因是通过wait系统调用读取的,读取完毕后进程由Z状态变成X状态。
设计一个僵尸状态:
1 #include<stdio.h>
2 #include<unistd.h>
3 #include<stdlib.h>
4 int main(){
5 int ret=fork();
6 if(ret<0){
7 perror("fork");
8 return 1;
9 }
10 else if(ret==0){//子进程两秒后退出
11 printf("child[%d] if begin Z...\n",getpid());
12 sleep(2);
13 exit(1);
14 }
15 else{
16 printf("parent[%d] id sleeping...\n",getpid());
17 sleep(5);
18 }
19 return 0;
20
21 }
僵尸进程的危害:
- 进程的退出状态必须被维持下去,因为父进程要读取退出原因。就像你叫给我任务,我得知道你做的怎么样。但是父进程一直不读取,那么子进程就一直出于Z状态。
- 退出信息是保持在PCB里的,如果父进程一直不读取,PCB就一直存在。
- 上面两条说明如果父进程创建了很多子进程,但是不回收,就会造成内存支援的浪费。因为PCB定义的对象要在内存中开辟空间。会造成内存泄漏。
3.2 孤儿进程
概念:父进程先退出,子进程就变成了"孤儿进程"。
思考一问题:如果父进程提前退出了,子进程退出后进入了僵尸状态,这个子进程没有父进程回收,就造成了内存泄漏了。这该如何处理呢?
操作系统考虑到了这个问题,将所有的孤儿进程被1号进程领养,意思就是让1号进程变成孤儿进程的子进程。
四.进程优先级
1.概念
- cpu资源是有限的,进程需要使用系统资源。cpu资源分配的先后顺序,就是指进程的优先级。
- 优先级高的进程由优先执行的权力,配置进程优先级对多任务环境的linux很有用,可以改善系统性能。
优先级其实是操作系统管理的一个分支,是为操作系统管理服务的,方便管理。
优先级与权限的区别:
优先级是在你能做这件事的基础上,做这件事的先后顺序。
权限是你能不能做这件事。
2.查看和修改
2.1 查看进程优先级
主要信息:
- UID:执行者的身份,我们一般用户名是我们自己取的,但是在系统中并不是我们取的名字,而是一个编号
- PID:进程id
- PPID:父进程id
- PRI:进程可被执行的优先级,值越小优先级越高
- NI:代表进程的nice指,属于优先级的修正数据。
优先级主要由两个参数控制PRI和NI。
2.2 PRI和NI
- PRI的值越小表示优先级越高,加入nice值后 PRI(new)=PRI(old)+NI。PRI(old)在centos下默认一直是80,NI值一直是0
- 在linux下调整进程优先级就是调整nice值,当nice值为负时,PRI会变小,优先级变高,反之优先级变低。
- nice值范围为-20到19,一共40级别。那么PRI的范围是60到99
这里要注意:优先级计算公式是:PRI(new)=PRI(old)+NI。优先级一直在80的基础上变,不管PRI(old)的值现在是多少。
PRI和NI不是一个概念,NI不是进程优先级,可以理解是进程的修正数据
2.3 修改进程
修改进程通过top命令,top命令相当于windows系统下的任务管理器。
顺序:top->按r->输入进程PID->输入nice值。
五.其它概念
- 竞争性:系统进程数目众多,而CPU的资源只有少量,,甚至只有1个,所以进程间是具有竞争属性的,为了高效完成任务,更合理竞争相关资源,变有了优先级。
- 独立性:多进程可以同时运行,每个进程需要独享资源,多进程运行期间互不干扰。
这里有一个时间片的概念:时间片是给进程分配占用CPU的时间的设备,自己有自己的算法。一个进程一般不会一直占用CPU直到进程结束,而是执行时间片分配的时间。执行完后按照优先级执行下一个进程。我们看到的多进程在执行,只是因为时间片给进程分配的时间很短,进程实际是穿插在运行,但我们的感知上觉得是同时在运行。
进程运行的时间不是严格确定的,而是在不断改变的,可能多了一个进程,每个进程的时间就会减少一点。
- 并行:多个进程在多个CPU下分别,同时进行运行。
- 并发:多个进程在一个CPU采用进程切换的方式,在一段时间内,让多个进程都得以推进。
并发是基于时间片轮转多个进程,看起来是同时在运行。
这样看来,支援在一个进程的时间变规定的时间里,这个进程是独享这一资源的,但是在一段时间里,支援相对于进程是共享的。
进程在时间片时间内,优先级高的进程不可抢占当前进程,叫做不可抢占式调度。
进程在时间片时间内,优先级高的进程可抢占当前进程,叫做可抢占式调度。这个主要是由调度器决定。但是现在主流是可抢占式调度。