Linux——了解进程

目录

一.进程概念

1.基本概念

2.进程描述——PCB

2.1 task_strcut和PCB的区别

2.2 task_struct内容分类

2.3 组织进程

2.4查看进程

2.5 通过系统调用获取进程标识符 

二.创建进程——fork(初识)

1.如何理解进程的创建

2.如何创建进程

 2.1 为什么fork会有两个返回值?

2.2 fork父子进程的执行顺序和代码数据的复制问题?

三.进程的状态

3.1 Z僵尸状态

3.2 孤儿进程

四.进程优先级

1.概念

2.查看和修改

2.1 查看进程优先级

2.2 PRI和NI

2.3 修改进程

 五.其它概念


一.进程概念

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 查看进程优先级

 主要信息:

  1. UID:执行者的身份,我们一般用户名是我们自己取的,但是在系统中并不是我们取的名字,而是一个编号
  2. PID:进程id
  3. PPID:父进程id
  4. PRI:进程可被执行的优先级,值越小优先级越高
  5. 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采用进程切换的方式,在一段时间内,让多个进程都得以推进。

并发是基于时间片轮转多个进程,看起来是同时在运行。

这样看来,支援在一个进程的时间变规定的时间里,这个进程是独享这一资源的,但是在一段时间里,支援相对于进程是共享的。

进程在时间片时间内,优先级高的进程不可抢占当前进程,叫做不可抢占式调度。

进程在时间片时间内,优先级高的进程可抢占当前进程,叫做可抢占式调度。这个主要是由调度器决定。但是现在主流是可抢占式调度。

  • 3
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值