[Linux][进程概念][进程优先级]详细解读

目录)

0.冯诺依曼体系结构

  • 截至目前,我们所认识的计算机,都是由一个个的硬件组件组成
    • 输入单元:包括键盘,鼠标,扫描仪,磁盘,网卡等
    • 输出单元:显示器,打印机,磁盘,网卡等
  • 注意:
    • 这里的存储器指的是内存
    • 不考虑缓存情况,这里的CPU能且只能对内存进行读写,不能访问外设(输入或输出设备)
    • 外设(输入或输出设备)要输入或者输出数据,也只能写入内存或者从内存中读取
    • 一句话总结,所有设备都只能直接和内存打交道
  • 对冯诺依曼的理解,不能停留在概念上,要深入到对软件数据流理解上

1.操作系统(Operator System)

1.概念

  • 任何计算机系统都包含一个基本的程序集合,称为操作系统(OS)
  • 笼统的理解,操作系统包括:
    • 内核(进程管理,内存管理,文件管理,驱动管理)
    • 其他程序(例如函数库,shell程序等等)

2.设计OS的目的

  • 与硬件交互,管理所有的软硬件资源
  • 为用户程序(应用程序)提供一个良好的执行环境

3.定位

  • 在整个计算机软硬件架构中,操作系统的定位是:一款纯正的"搞管理"的软件

4.系统调用和库函数概念

  • 在开发角度,操作系统对外会表现为一个整体,但是会暴露自己的部分接口,供上层开发使用,这部分由操作系统提供的接口 ,叫做系统调用
  • 系统调用在使用上,功能比较基础,对用户的要求相对也比较高,所以,有心的开发者可以对部分系统调用进行适度封装,从而形成库,有了库,就很有利于更上层用户或者开发者进行二次开发

5.总结

  • 计算机管理 硬件:先描述,再管理
    • 描述起来,用struct结构体
    • 组织起来,用链表或其他高效的数据结构

2.进程

1.基本概念

  • 课本概念:程序的一个执行实例,正在执行的程序等
  • 内核观点:担当分配系统资源(CPU时间,内存)的实体
  • 什么叫做进程?
    • 进程 = 对应的代码和数据 + 进程对应的PCB结构体
    • 文件 = 内容 + 属性

2.描述进程 -- PCB

  • 进程信息被放在一个叫做进程控制块 的数据结构中,可以理解为进程属性的集合
    • PCB(Process Control Block),Linux操作系统下的PCB是: task_struct
    • task_struct是Linux内核的一种数据结构,它会被装载到RAM(内存)里并且包含着进程的信息

3.task_struct内容分类

  • **标示符:**描述本进程的唯一标示符,用来区别其他进程
  • **状态:**任务状态,退出代码,退出信号等
  • **优先级:**相对于其他进程的优先级
  • **程序计数器:**程序中即将被执行的下一条指令的地址
  • **内存指针:**包括程序代码和进程相关数据的指针,还有和其他进程共享的内存块的指针
  • 上下文数据:进程执行时处理器的寄存器中的数据[休学例子,要加图CPU,寄存器]
  • I/O状态信息:包括显示的I/O请求,分配给进程的I/O设备和被进程使用的文件列表
  • **记账信息:**可能包括处理器时间总和,使用的时钟数总和,时间限制,记账号等
  • 其他信息

4.组织进程

  • 可以在内核源代码里找到它。所有运行在系统里的进程都以task_struct链表的形式存在内核里

5.查看进程

  • 进程的信息可以通过 /proc 系统文件夹查看
    • 如:要获取PID为1的进程信息,你需要查看 /proc/1 这个文件夹
    • 大多数进程信息同样可以使用topps这些用户级工具来获取
sh 复制代码
ps ajx | grep 'filename'  
ps ajx | head -1 && ps ajx | grep 'filename'

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

  • 进程id(PID) --> getpid()
  • 父进程id(PPID) --> getppid()

7.通过系统调用创建进程 -- fork初识

  • 运行 man fork 认识fork
  • fork****有两个返回值
    • 为什么会有两个返回值?
      • 子进程内部属性,要以父进程为模板
      • fork内部,父子各自会执行自己的return语句
    • 失败的时候:返回-1
    • 成功的时候:
      • 给父进程返回子进程的pid
      • 给子进程返回0
  • fork****之后,代码是父子共享的
  • fork****之后通常要用 if 进行分流
cpp 复制代码
	pid_t id = fork();
	if(id < 0)
	{
	    //创建失败
	    perror("fork");
	    return 1;
	}
	else if(id == 0)
	{
	    //child process(task)
	    printf("I am child, pid: %d, ppid: %d\n", getpid(), getppid());
	    sleep(1);
	}
	else 
	{
	    //parent process
	    printf("I am father, pid: %d, ppid: %d\n", getpid(), getppid());
	    sleep(1);
	}

8.进程状态

  • 新建
  • **运行:**task_struct结构体在运行队列中排队 --> 运行态
  • **阻塞:**等待非CPU资源就绪 --> 阻塞状态
    • 进程阻塞本质 --> 进程阻塞在系统函数的内部
  • **挂起:**当内存不足的时候,OS通过适当的置换进程的代码和数据到磁盘,进程的状态就叫做挂起
cpp 复制代码
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):并不意味着进程一定在运行中,它表明进程要么是在运行中要么在运行队列里
  • S 睡眠状态(sleeping):对应着上面的阻塞 状态,意味着进程在等待事件完成(这里的睡眠有时候也叫做可中断睡眠(interruptible sleep))
  • D 磁盘休眠状态(Disk sleep)睡眠状态,磁盘睡眠,深度睡眠,也叫不可中断睡眠状态 (uninterruptible sleep),不可以被被动唤醒,在这个状态的进程通常会等待IO的结束
  • T停止状态(stopped): 可以通过发送SIGSTOP信号给进程来停止进程。这个被暂停的进程可以通过发送SIGCONT信号让进程继续运行
  • X死亡状态(dead):这个状态只是一个返回状态,不会在任务列表里看到这个状态

9.进程状态查看

sh 复制代码
ps aux / ps axj 命令

10.僵尸进程

  • 僵死状态(Zombies)是一个比较特殊的状态
    • 当进程退出并且父进程(使用wait()系统调用)没有读取到子进程退出的返回代码时就会产生僵尸进程
    • 僵尸进程会以终止状态保持在进程表中,并且会一直在等待父进程读取退出状态代码
    • 所以,只要子进程退出,父进程还在运行,但父进程没有读取子进程状态,子进程就进入Z状态
  • 综上,即一个进程已经退出,但是还不允许被OS释放,处于一个被检测的状态 --> 僵尸状态
  • 以下为一个创建维持30s的僵尸进程的例子
cpp 复制代码
std::cout << "I am parent process! -- PID:" << getpid() << std::endl;

pid_t ret = fork();
// 变成两个进程,一个是父进程,一个是子进程
if(ret < 0)
{
    perror("fork failed");
    return 1;
}
else if (ret > 0)
{
    // parent process
    std::cout << "parent:" << getpid() << ":is sleeping" << std::endl;
    sleep(30);
}
else
{
    // child process
    std::cout << "child:" << getpid() << ":is sleeping" << std::endl;
    sleep(5);
    exit(0);
}

11.僵尸进程危害

  • 进程的退出状态必须被维持下去,因为它要告诉关心它的进程(父进程),你交给我的任务,我办的怎么样了
    • 可父进程如果一直不读取,那子进程就一直处于Z状态? --> 是的!
  • 维护退出状态本身就是要用数据维护,也属于进程基本信息,所以保存在task_struct(PCB)中
    • 换句话说,Z状态一直不退出,PCB一直都要维护? --> 是的!
  • 那一个父进程创建了很多子进程,就是不回收,是不是就会造成内存资源的浪费? --> 是的!
    • 因为数据结构对象本身就要占用内存,想想C中定义一个结构体变量(对象),是要在内存的某个位置开辟空间!
  • 以上是否构成内存泄漏? --> 是的!

12.孤儿进程

  • 父进程先退出,子进程就称之为"孤儿进程"
  • 父进程如果提前退出,那么子进程后退出,进入Z之后,那该如何处理呢?
  • 孤儿进程被1号init进程领养,当然要由init进程回收
  • 为什么要被领养?
  • 未来子进程退出的时候,父进程早已不在,需要领养进程来进行回收

13.守护进程&精灵进程

  • 这两种是同一种进程的不同翻译,是特殊的孤儿进程
    • 不但运行在后台,最主要的是脱离了与终端和登录会话的所有联系,也就是默默的运行在后台不想受到任何影响

3.进程优先级

1.基本概念

  • CPU资源分配的先后顺序,就是指进程的优先权(priority)
  • 优先权高的进程有优先执行权利
    • 配置进程优先权对多任务环境的Linux很有用,可以改善系统性能
    • 还可以把进程运行到指定的CPU上,这样一来,把不重要的进程安排到某个CPU,可以大大改善系统整体性能

2.查看系统进程

  • 在Linux或者unix系统中,用ps --l 命令则会类似输出以下几个内容

  • 几个重要信息:

    • UID : 代表执行者的身份
    • PID : 代表这个进程的代号
    • PPID :代表这个进程是由哪个进程发展衍生而来的,亦即父进程的代号
    • PRI :代表这个进程可被执行的优先级,其值越小越早被执行
    • NI :代表这个进程的nice值

3.PRI & NI

  • PRI,即进程的优先级,通俗点说就是程序被CPU执行的先后顺序,此值越小进程的优先级别越高
  • NI,即nice值,其表示进程可被执行的优先级的修正数值
  • PRI值越小越快被执行,那么加入nice值后,将会使得PRI变为:PRI(new)=PRI(old)+nice
    • 这样,当nice值为负值的时候,那么该程序将会优先级值将变小,即其优先级会变高,则其越快被执行
    • 所以,调整进程优先级,在Linux下,就是调整进程nice值
    • **PRI(old)**都是80,每次设置优先级,都要从进程最开始的优先级开始设置
  • nice其取值范围是**-20至19**,一共40个级别

4.PRI vs NI

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

5.查看进程优先级的命令

  • top 命令更改已存在进程的nice:
    • 进入top后按"r"-->输入进程PID-->输入nice值

6.其他概念

  • 竞争性:系统进程数目众多,而CPU资源只有少量,甚至1个,所以进程之间是具有竞争属性的。为了高效完成任务,更合理竞争相关资源,便具有了优先级
  • 独立性:多进程运行,需要独享各种资源,多进程运行期间互不干扰,父子进程也具有独立性
  • 并行 :多个进程在多个CPU下分别,同时进行运行,这称之为并行
  • 并发 :多个进程在一个CPU 下采用进程切换的方式 ,在一段时间之内,让多个进程都得以推进,称之为并发
    • 时间片
    • 抢占与出让

7.什么是进程切换?

  • OS在多任务环境下,将CPU的执行控制从一个正在执行的进程切换到另一个进程的过程
    • 它确保多个进程能够共享CPU时间,并在一个系统上同时运行多个任务,使系统能够高效地利用计算资源
  • 上下文切换:
    • 上下文是指OS维护的关于进程状态和寄存器内容的信息
    • 当OS决定切换到另一个进程时,它会保存当前执行进程的上下文,包括寄存器的内容、程序计数器和其他与进程相关的信息
  • **原因:**进程切换可以由多种原因触发,包括进程的时间片耗尽、等待I/O完成、信号的到来、进程主动放弃CPU(例如,通过系统调用yield或sleep),或者新进程被调度执行
  • 调度器
    • Linux使用调度器来选择下一个要运行的进程,调度器基于一些策略和优先级来选择要执行的进程
    • 这确保了高优先级的任务得到更多的CPU时间,并使系统对多个任务进行公平分配
  • 切换过程:
    • 当发生进程切换时,操作系统会保存当前进程的上下文,选择下一个要运行的进程,然后还原其上下文,以便它从中断的地方继续执行
    • 这个过程包括在内核态和用户态之间的切换,因为内核需要在不同的进程之间进行操作
      • 进程切换通常包括大量的操作,如保存和恢复寄存器、切换内存映射、刷新页表等,以确保新进程能够正确执行
  • 性能影响:
    • 进程切换是有开销的,因为它涉及到复杂的操作。过多的进程切换会导致系统性能下降,因此操作系统的调度算法需要权衡各个进程的需求,以最大程度地减少不必要的切换
相关推荐
xiaozhiwise30 分钟前
Makefile 之 自动化变量
linux
Kkooe1 小时前
GitLab|数据迁移
运维·服务器·git
久醉不在酒2 小时前
MySQL数据库运维及集群搭建
运维·数据库·mysql
意疏3 小时前
【Linux 篇】Docker 的容器之海与镜像之岛:于 Linux 系统内探索容器化的奇妙航行
linux·docker
虚拟网络工程师3 小时前
【网络系统管理】Centos7——配置主从mariadb服务器案例(下半部分)
运维·服务器·网络·数据库·mariadb
BLEACH-heiqiyihu3 小时前
RedHat7—Linux中kickstart自动安装脚本制作
linux·运维·服务器
一只爱撸猫的程序猿3 小时前
一个简单的Linux 服务器性能优化案例
linux·mysql·nginx
勤奋的小王同学~3 小时前
项目虚拟机配置测试环境
服务器
007php0073 小时前
GoZero 上传文件File到阿里云 OSS 报错及优化方案
服务器·开发语言·数据库·python·阿里云·架构·golang