Linux进程概念

Linux进程概念

1.进程的理解

1.1 进程的概念

基本概念:

  • 操作系统理论概念:程序的一个执行实例,正在执行的程序等
  • Linux内核观点:担当分配系统资源(CPU时间,内存)的实体
  • 核心理解:进程=可执行程序+该进程对应的内核数据结构

进程控制块(PCB)的理解:

  • 进程信息被放在一个叫做进程控制块的数据结构中,可以理解为进程属性的集合
  • 操作系统理论上称之为PCB(process control block)Linux操作系统下的PCB是: task_struct
  • 凡是提到进程,必须首先想到进程task_struct(PCB)
  • PCB是操作系统描述进程的一个统称。当可执行程序加载到内存,是运行了一个进程。实际的大小要比文件本身要大。操做系统管理进程要先对进程进行描述,会添加一些属性(所以比本身文件大),属性包括描述信息+内容代码数据等
  • 操作系统允许多个进程同时允许,为了方便管理,操作系统还会将进程组织起来,一般是组织成一个双向链表的数据结构,如下图:

请添加图片描述


1.2 task_struct与PCB的区别

  • PCB是操作系统描述进程的一个统称
  • task_struct是Linux下描述进程的结构体是Linux内核的一种数据结构,它会被装载到内存里并且包含进程的信息

1.3 task_struct的内容分类(了解)

task_ struct内容分类:

标示符: 描述本进程的唯一标示符,用来区别其他进程

状态: 任务状态,退出代码,退出信号等

优先级: 相对于其他进程的优先级

程序计数器: 程序中即将被执行的下一条指令的地址

内存指针: 包括程序代码和进程相关数据的指针,还有和其他进程共享的内存块的指针

上下文数据: 进程执行时处理器的寄存器中的数据[休学例子,要加图CPU,寄存器]

I/O状态信息: 包括显示的I/O请求,分配给进程的I/O设备和被进程使用的文件列表

记账信息: 可能包括处理器时间总和,使用的时钟数总和,时间限制,记账号等

其他信息


1.4 组织进程和查看进程的方法

查看进程:进程的信息可以通过 /proc 系统文件夹查看

如果要获取PID为1的进程信息,你需要查看 /proc/1 这个文件夹

请添加图片描述

请添加图片描述

大多数进程信息也可以使用top和ps这些用户级工具获取

单独使用ps命令,会显示所有进程信息:

ps aux

ps命令与grep命令搭配使用,即可只显示某一进程的信息:

ps aux | head -1 && ps aux | grep proc | grep -v grep

请添加图片描述

  1. 可以使用命令 kill -9 pid 杀死进程
  2. proc文件:内存文件系统—当前系统实时的进程信息
  3. 每一个进程在系统中,存在一个唯一的标识符PID

1.5 进程标识符的获取方法

方法一:通过getpid()、getppid()函数获取

通过系统调用获取进程标示符:

  1. 子进程标识符(PID):getpid()
  2. 父进程标识符(PPID):getppid()

举例代码:

#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
int main()
{
     printf("pid: %d\n", getpid());
     printf("ppid: %d\n", getppid());
     return 0;
}

请添加图片描述


方法二:通过fork()函数获取

前提知识:

  1. fork是一个系统调用级别的函数,其功能就是创建一个子进程
  2. fork执行一次,有两个返回值父进程返回子进程PID子进程返回0
  3. 父子进程代码共享,数据各自开辟空间,私有一份(采用写时拷贝)

举例代码:打印父子进程pid:

 #include<stdio.h>
 #include<sys/types.h>
 #include<unistd.h>
  
 int main()
 {
     pid_t id=fork();
     //理论上:id:0 子进程  id>0 父进程
     //实际上:子进程、父进程各自得到一个id进行条件判断
     //父进程得到的变量id是子进程pid,子进程得到的id是0
     if(id==0)
     {
       //child
       while(1)
       {
          printf("我是子进程,我的pid:%d,我的父进程是:%d\n",getpid(),getppid());                       
          sleep(1);
        }
      }
      else
      {
        //parent
       	printf("我是父进程,我的pid:%d,我的父进程是:%d\n",getpid(),getppid());
       	sleep(1);
      }
     return 0;
}

请添加图片描述

请添加图片描述

奇怪的现象:

  • 这里遇到了一个奇怪的问题:这里的if和else同时执行了,进行了两次printf打印,并没有受到条件的约束,为什么呢?
  • 首先我们思考,C语言上if 和else if可以同时执行嘛?当然不可以。有没有可能两个以上死循环?当然不可以
  • 但是这里却发现Linux下可以!

出现这个现象的原因:

fork之后,父进程和子进程会共享代码,也就是写时拷贝了代码这就解释了为什么printf会打印两次的原因

fork之后,父进程和子进程返回值不同,可以通过不同的返回值,进行判断让父子执行不同的代码块这就解释了为什么条件约束失效


1.6 fork()函数有两个返回值的原因剖析

重点剖析:fork()为什么给父进程返回子进程pid,给子进程返回0?

父子关系:父亲—儿子:1:n(n>=1)

原因:

  1. 父进程必须要有标识子进程的方法:fork()之后,给父进程返回子进程的pid!
  2. 子进程最重要的是要知道自己被创建成功了,因为子进程找父进程成本非常低—使用getppid()

重点剖析:fork()之后父子进程谁先执行

  1. 通常情况下,我们总是用 sleep 等操作来保证另一个进程先执行,但父子进程谁先执行并不是不可预测的
  2. 从linux内核2.6.32开始,在默认情况下,父进程将成为fork之后优先调度的对象
  3. 采取这种策略的原因很简单:fork是父进程发起的调用,因此fork之后,父进程在CPU中处于活跃的状态,并且其内存管理信息也被置于硬件内存单元的转译后备缓冲器(TLB),所以先调度父进程无论从减少上下文切换、CPU让出等方面都可以提高性能
  4. linux内核从2.6.24开始,内核采用完全公平调度(CFS),用户创建的普通进程,都采用CFS调度策略。对于CFS调度策略,内核提供的控制选项默认是0,表示父进程优先获得调度,如果该值被改为1,子进程会优先获得调度。但POSIX标准和linux都没有保证会优先调度父进程,因此在应用中,我们不能对父子进程的执行顺序做任何假设
  5. 如果确实需要父子进程的某一特定执行顺序,那么还是得需要进程间的同步手段
  6. 所以:从理论上,优先调度父进程。从使用上,不可预测。

结论:进程调度顺序由操作系统内核调度算法共同决定

重点剖析:为什么fork()会返回两次?

前提分析:

请添加图片描述

原因分析:

  1. 当程序执行到下面的语句: pid=fork(); 由于在复制时复制了父进程的堆栈段,所以两个进程都停留在fork函数中,等待返回。 因此fork函数会返回两次,一次是在父进程中返回,另一次是在子进程中返回,这两次的返回值是不一样的。 fork调用的一个奇妙之处就是它仅仅被调用一次,却能够返回两次,它可能有三种不同的返回值:
    • 在父进程中,fork返回新创建子进程的进程PID
    • 在子进程中,fork返回0
    • 如果出现错误,fork返回一个负值
  2. 我们可以通过fork返回的值来判断当前进程是子进程还是父进程,其实就相当于链表,进程形成了链表,父进程的fork函数返回的值指向子进程的进程PID, 因为子进程没有子进程,所以其fork函数返回的值为0
  3. 调用fork之后,数据、堆、栈有两份,代码仍然为一份但是这个代码段成为两个进程的共享代码段都从fork函数中返回。当父子进程有一个想要修改数据或者堆栈时,两个进程真正分裂
  4. 子进程代码是从fork处开始执行的, 为什么不是从#include处开始复制代码的?这是因为fork是把进程当前的情况拷贝一份,执行fork时,进程已经执行完了当前情况; fork只拷贝下一个要执行的代码到新的进程
    请添加图片描述

2.进程状态

2.1 Linux的进程状态

Linux系统的进制状态分类:

  1. 运行状态(R,running)并不意味着进程一定在运行中,它表明进程要么是在运行中要么在运行队列里

  2. 睡眠状态(S,sleeping)意味着进程在等待事件完成,这里的睡眠有时候也叫做可中断睡眠 interruptible sleep

  3. 磁盘休眠状态(D,Disk sleep)有时候也叫不可中断睡眠状态(uninterruptible sleep)在这个状态的进程通常会等待IO的结束

  4. 暂停状态(T,stopped)可以通过发送 SIGSTOP 信号给进程来停止(T)进程。这个被暂停的进程可 以通过发送 SIGCONT 信号让进程继续运行

  5. 死亡状态(X,dead)这个状态只是一个返回状态,你不会在任务列表里看到这个状态

  6. 僵尸状态(Z,zombie)是一个比较特殊的状态。当进程退出并且父进程,没有读取到子进程退出的返回代码时就会产生僵死(尸)进程

  7. 杀死进程的方法:kill -9 进程PIDctrl+c只能杀前台进程,kill命令可以杀后台进程

  8. Linux内核进程状态源代码:

    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.2 进程阻塞与进程挂起的理解

进程阻塞:

1.一个进程使用资源的时候,可不仅仅是在申请CPU资源

2.进程可能申请更多的其他资源:磁盘,网卡,显卡等

3.如果我们申请CPU资源,暂时无法得到满足,是需要排队的,即进入运行队列

请添加图片描述

进程挂起:

1.挂起进程在操作系统中可以定义为暂时被淘汰出内存的进程,机器的资源是有限的,在资源不足的情况下,操作系统对在内存中的程序进行合理的安排,其中有的进程被暂时调离出内存,当条件允许的时候,会被操作系统再次调回内存,重新进入等待被执行的状态,表现为系统在一定的时间没有任何动作

请添加图片描述


2.3 进程状态查看方法

进程状态的查看:ps aux / ps axj

//常用的进程状态查看方法
ps aux|head -1 && ps aux|grep 进程PID
ps ajx|head -1 && ps ajx|grep 进程PID

请添加图片描述

  1. 系统原生的用法不好用,如下图:
  2. ps aux:显示所有进程 有效用户ID或名字
  3. ps ajx:显示所有进程 PPID、PID、PGID、SID、COMMAND等

请添加图片描述

ps a //显示现行终端机下的所有程序,包括其他用户的程序

ps u   //以用户为主的格式来显示程序状况。

ps x   //显示所有程序,不以终端机来区分

ps aux //显示有效用户ID或名字

ps ajx //显示PPID,PID,PGID,SID,UID,COMMAND等

ps awx //显示完全程序参数


2.4 进程状态:运行状态R

测试方法:使用命令top,查看进程状态

效果:

请添加图片描述

这里我们可以看到一些进程状态时R,这些标识为R的进程就是正在运行或者进入运行队列的进程


2.5 进程状态:睡眠状态S

测试代码:

#include<stdio.h>
int main()
{
    printf("Process is running...\n");
    printf("Process PID:%d\n",getpid());
    sleep(1000);
    return 0;
}

效果:

请添加图片描述

  1. 这里我们看到子进程8130的运行状态就是S状态
  2. 一个进程处于浅度睡眠状态(sleeping),意味着该进程正在等待某件事情的完成,处于浅度睡眠状态的进程随时可以被唤醒,也可以被杀掉(这里的睡眠有时候也可叫做可中断睡眠(interruptible sleep))

2.6 进程状态:深度睡眠状态D

  1. 一个进程处于深度睡眠状态(disk sleep),表示该进程不会被杀掉,即便是操作系统也不行,只有该进程自动唤醒才可以恢复。该状态有时候也叫不可中断睡眠状态(uninterruptible sleep),处于这个状态的进程通常会等待IO的结束
  2. 例如,某一进程要求对磁盘进行写入操作,那么在磁盘进行写入期间,该进程就处于深度睡眠状态,是不会被杀掉的,因为该进程需要等待磁盘的回复(是否写入成功)以做出相应的应答。(磁盘休眠状态)

2.7 进程状态:暂停状态T

在Linux当中,我们可以通过发送SIGSTOP信号使进程进入暂停状态(stopped),发送SIGCONT信号可以让处于暂停状态的进程继续运行

例如:

请添加图片描述

我们再对该进程发送SIGCONT信号,该进程就继续运行了:

请添加图片描述


2.8 进程状态:死亡状态X

  1. 死亡状态只是一个返回状态,当一个进程的退出信息被读取后,该进程所申请的资源就会立即被释放,该进程也就不存在了,所以你不会在任务列表当中看到死亡状态(dead)

2.9 进程状态:僵尸状态Z*

  1. 僵死状态(Zombies)是一个比较特殊的状态。当进程退出并且父进程(使用wait()系统调用,后面讲)没有读取到子进程退出的返回代码时就会产生僵死(尸)进程
  2. 僵死进程会以终止状态保持在进程表中,并且会一直在等待父进程读取退出状态代码
  3. 所以,只要子进程退出,父进程还在运行,但父进程没有读取子进程状态,子进程进入Z状态

举例代码:

#include<stdio.h>
#include<unistd.h>
#include<stdlib.h>
int main()
{
    printf("I am running...\n");
    pid_t id=fork();
    if(id==0)
    {
        //child
        int count=3;
        while(count)
        {
            printf("I am chile,pid:%d,ppid:%d,count:%d\n",getpid(),getppid(),--count);
            sleep(1);    
        }
        printf("child quit...\n");
        exit(1);
    }
    else if(id>0)
    {
        //father
        while(1)
        {
            printf("I am father,pid:%d,ppid:%d\n",getpid(),getppid());
            sleep(1);
        }
    }
   return 0;
}

请添加图片描述

从上图我们就看到了子进程24337的状态由S+变成了Z+,进入僵尸状态的进程我们称它为僵尸进程!

僵尸进程对于我们的操作系统来说是有极大危害的!

僵尸进程危害:

1.进程的退出状态必须被维持下去,因为他要告诉关心它的进程(父进程),你交给我的任务,我办的怎么样了。可父进程如果一直不读取,那子进程就一直处于Z状态

2.维护退出状态本身要用数据维护,也属于进程基本信息,所以保存在task_struct(PCB)中,换句话说,Z状态一直不退出,PCB一直都要维护

3.那一个父进程创建了很多子进程,就是不回收,就会造成内存资源的浪费,因为数据结构对象本身就要占用内存,想想C中定义一个结构体变量(对象),是要在内存的某个位置进行开辟空间!

4.导致内存泄漏【常见问题:1.频繁GC(垃圾回收机制),发生GC的时候,所有进程都必须等待,GC频率越高,就感觉系统越卡顿2.内存不足引发的程序运行崩溃


2.10 特殊的进程状态:孤儿状态

  1. 若子进程先退出而父进程没有对子进程的退出信息进行读取,那么我们称该进程为僵尸进程
  2. 但若是父进程先退出,那么将来子进程进入僵尸状态时就没有父进程对其进行处理,此时该子进程就称之为"孤儿进程"
  3. 孤儿进程被1号init进程领养,当然要有init进程回收,1号init就是操作系统
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
int main()
{
     pid_t id = fork();
     if(id < 0)
     {
         perror("fork");
         return 1;
     }
     else if(id == 0)
     {//child
         printf("I am child, pid : %d\n", getpid());
         sleep(10);
     }else
     {//parent
         printf("I am parent, pid: %d\n", getpid());
         sleep(3);
         exit(0);
     }
     return 0;
}

请添加图片描述


3.进程优先级

3.1 进程优先级基本概念

cpu资源分配的先后顺序,就是指进程的优先权(priority)

优先权高的进程有优先执行权利。配置进程优先权对多任务环境的linux很有用,可以改善系统性能

还可以把进程运行到指定的CPU上,这样一来,把不重要的进程安排到某个CPU,可以大大改善系统整体性能

核心问题:

1.什么是优先级?什么是权限?

优先级是进程获取资源的先后顺序

权限是能还是不能获取资源

2.为什么存在优先级?

排队的本质叫做确定优先级,资源总是不够的,需要优先分配给重要的进程

3.查看进程优先级方法?

//Linux Unix系统下,用ps -l命令监控运行的进程的详细信息
ps -l

请添加图片描述

UID : 代表执行者的身份

PID : 代表这个进程的代号

PPID :代表这个进程是由哪个进程发展衍生而来的,亦即父进程的代号

PRI :代表这个进程可被执行的优先级,其值越小越早被执行

NI :代表这个进程的nice值


3.2 对于PRI和NI的理解

  • PRI代表进程的优先级(priority),通俗点说就是进程被CPU执行的先后顺序,该值越小进程的优先级别越高,默认是80
  • NI代表的是nice值,其表示进程可被执行的优先级的修正数值
  • PRI值越小越快被执行,当加入nice值后,将会使得PRI变为:PRI(new) = PRI(old) + NI
  • 若NI值为负值,那么该进程的PRI将变小,即其优先级会变高
  • 调整进程优先级,在Linux下,就是调整进程的nice值
  • NI的取值范围:-20~19,40个级别
  • PRI的取值范围:60~99,每次设置优先级都会被恢复为默认的80
  • Linux不允许进程无节制的设置优先级

注:需要强调一点的是,进程的nice值不是进程的优先级,,他们不是一个概念,但是进程nice值会影响到进程的优先级变化。 可以理解nice值是进程优先级的修正数据


3.3 查看和修改进程优先级命令

  1. 查看进程优先级的命令top
  2. top命令用于显示系统运行的进程信息,作用类似于windows中的任务管理器,只不过top不是图形化的,而是显示实时文本信息
  3. 修改进程优先级的命令修改进入top后按“r”–>输入进程PID–>输入nice值
  4. 注:每次设置优先级,这个PRI(old)优先级都会恢复为80

修改步骤:

  1. 输入top,查看进程优先级

请添加图片描述

  1. 输入r,进入修改功能

请添加图片描述

  1. 输入要修改的PID并输入nice值,这里以PID 3155举例,我们改nice为19

请添加图片描述

  1. 最后我们来看看3155进程的NI就变成了19

请添加图片描述

这里就可以很清晰的看到了,我们原来的3155进程PR是20,NI修改为19后,PR就变成了39=20+19

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

「已注销」

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值