[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时间,并使系统对多个任务进行公平分配
  • 切换过程:
    • 当发生进程切换时,操作系统会保存当前进程的上下文,选择下一个要运行的进程,然后还原其上下文,以便它从中断的地方继续执行
    • 这个过程包括在内核态和用户态之间的切换,因为内核需要在不同的进程之间进行操作
      • 进程切换通常包括大量的操作,如保存和恢复寄存器、切换内存映射、刷新页表等,以确保新进程能够正确执行
  • 性能影响:
    • 进程切换是有开销的,因为它涉及到复杂的操作。过多的进程切换会导致系统性能下降,因此操作系统的调度算法需要权衡各个进程的需求,以最大程度地减少不必要的切换
相关推荐
2302_7995257413 分钟前
【Hadoop】Hadoop集群安装中出现的问题
linux·hadoop
MoloXuanhe19 分钟前
[TryHackMe]Wordpress: CVE-2021-29447(wp漏洞利用-SSRF+WpGetShell)
运维·网络·安全·tryhackme·thm
刘一说19 分钟前
Linux调试命令速查:Java/微服务必备
java·linux·微服务
枫の准大一30 分钟前
【Linux游记】基础指令篇
linux
wanhengidc32 分钟前
网页版的云手机都有哪些优势?
运维·网络·安全·游戏·智能手机
ypf520841 分钟前
OrbStack 配置国内镜像加速
linux
Hello.Reader1 小时前
一文通关 Proto3完整语法与工程实践
java·linux·数据库·proto3
Hello.Reader1 小时前
一文吃透 Protobuf “Editions” 模式从概念、语法到迁移与实战
linux·服务器·网络·protobuf·editions
陌上花开缓缓归以1 小时前
linux ubi文件系统
linux
2418ly1 小时前
docker常用命令
运维·docker·容器