深入解析Linux进程概念与操作系统核心
本文摘要:计算机系统遵循冯诺依曼体系结构,由输入单元、中央处理器(CPU)、内存和输出单元组成,所有设备只能直接与内存交互。操作系统(OS)作为管理软件,负责管理硬件资源并为用户程序提供稳定环境,其核心功能包括进程管理、内存管理等。进程是程序执行的实例,由内核数据结构(如task_struct)和代码数据组成,通过PCB进行管理。进程状态包括运行、阻塞、挂起等,僵尸进程和孤儿进程是两种特殊状态,需
目录
1. 冯诺依曼体系结构
我们常⻅的计算机,如笔记本。我们不常⻅的计算机,如服务器,⼤部分都遵守冯诺依曼体系。
截⾄⽬前,我们所认识的计算机,都是由⼀个个的硬件组件组成
• 输⼊单元:包括键盘,⿏标,扫描仪,写板等
• 中央处理器(CPU):含有运算器和控制器等
• 输出单元:显⽰器,打印机等
关于冯诺依曼,必须强调⼏点:
• 这⾥的存储器指的是内存
• 不考虑缓存情况,这⾥的CPU能且只能对内存进⾏读写,不能访问外设(输⼊或输出设备)
• 外设(输⼊或输出设备)要输⼊或者输出数据,也只能写⼊内存或者从内存中读取。
• ⼀句话,所有设备都只能直接和内存打交道。
2. 操作系统(Operator System)
2-1 概念
任何计算机系统都包含⼀个基本的程序集合,称为操作系统(OS)。笼统的理解,操作系统包括:
• 内核(进程管理,内存管理,⽂件管理,驱动管理)
• 其他程序(例如函数库,shell程序等等)
2-2 设计OS的⽬的
• 对下,与硬件交互,管理所有的软硬件资源
• 对上,为⽤⼾程序(应⽤程序)提供⼀个良好的执⾏环境
、为什么要有操作系统?
1.如果要让用户自己去对底层软硬件资源进行操作,成本太高了,因此由OS对下进行软硬件资源的管理,从而确保操作过程是稳定、安全、高效的,进行良好的工作!
2.OS对上给用户提供一个稳定、安全、高效的运行环境。
注意:用户不能直接对硬件进行操作,必须经过操作系统之手!
2-3 核⼼功能
• 在整个计算机软硬件架构中,操作系统的定位是:⼀款纯正的“搞管理”的软件
2-4 如何理解 "管理"
Linux操作系统管理的本质在于“先描述,再组织”,拿OS管理硬件来说:
先描述:将每个硬件的所有属性封装起来,形成一个结构体或类对象。
再组织:将这些结构体放到一个数据结构里(拿链表来说),那么对某个硬件的操作,就变成了对链表的增删查改操作了!
注意:对于任何计算机对象,管理的原则都是先描述再组织!
2-5 系统调⽤和库函数概念
• 在开发⻆度,操作系统对外会表现为⼀个整体,但是会暴露⾃⼰的部分接⼝,供上层开发使⽤, 这部分由操作系统提供的接⼝,叫做系统调⽤。
• 系统调⽤在使⽤上,功能⽐较基础,对⽤⼾的要求相对也⽐较⾼,所以,有⼼的开发者可以对部 分系统调⽤进⾏适度封装,从⽽形成库,有了库,就很有利于更上层⽤⼾或者开发者进⾏⼆次开 发。
总结:系统调用是操作系统为用户开放的接口,但由于使用成本高,用户对系统调用进行了封装,形成了库函数;
3. 进程
3-1 基本概念与基本操作
课本概念:进程是一个正在运行中的程序;
而从内核来说:进程 = 内核数据结构 (task_struct)+ 代码和数据;
3-2 描述进程-PCB
基本概念:
• 进程信息被放在⼀个叫做进程控制块的数据结构中,可以理解为进程属性的集合。
• 课本上称之为PCB(process control block),Linux操作系统下的PCB是:task_struct
task_struct其实是PCB的⼀种;
• 在Linux中描述进程的结构体叫做task_struct。
• task_struct是Linux内核的⼀种数据结构,它会被装载到RAM(内存)⾥并且包含着进程的信息。
进程的管理:
从图中可以看出,当可执行程序运行后,代码数据会被加载到内存中,而关于进程的各种属性,以及指向代码数据的指针都被封装在task_struct中,然后将这个结构体放入链表中管理起来,这不就是前面说的“先描述再组织”吗?
一个task_struct,就能找到进程的各种属性以及代码数据,所以我们说进程就=内核数据结构+代码和数据;
3-3 task_struct
内容分类:
组织进程
可以在内核源代码⾥找到它。所有运⾏在系统⾥的进程都以task_struct链表的形式存在内核⾥。
3-4 查看进程的方式
①在/proc文件里查看,这里有所有进程的信息
如:要获取PID为1的进程信息,你需要查看 /proc/1 这个⽂件夹
进入某个进程文件后,会有exe和cwd两项,exe代表可执行文件所在的路径,cwd代表当前工作路径(可以通过chdir函数改变工作路径),可以用ls-l查看路径:
②通过指令ps -ajx 也可以调出所有进程的信息,如果想查找特定进程,可按照如下管道的方式进行
上图中可以查找到进程的pid,pid是用来唯一标识进程的标识符,此外还可以通过系统调用函数getpid来打印pid值。ppid可以通过getppid获取;
4.父子进程
4-1:问题引入
当我们的操作系统启动后,就会有一个bash进程开始执行了,观察图中代码:myproc程序每次运行,都会打印出相同的ppid,也就是父进程都一样,那么可以得出结论:在命令行中,执行命令、执行程序的本质都是bash进程创建子进程,由子进程来执行我们的代码!
4-2 通过系统调⽤创建进程-fork初识
①见一见子进程创建
②快速解释fork函数
fork函数能创建一个子进程,这样就会有父进程和子进程,对父进程的返回值是子进程的pid,对子进程返回的是0,创建失败返回-1.
子进程创建出来后,父子进程的代码是共享的,但是数据是各自私有的!
原因:进程之间具有很强的独立性,多个进程之间是互不影响的,即使是父子进程;
注意:子进程创建后,为了方便管理,也会有相应的task_struct来管理子进程;子进程刚创建时,和父进程共享一份代码(共享同一物理内存页),但是一旦某一进程修改了数据,就会多拷贝一份代码数据,以保持数据独立性;
说的再具体一些,fork采用的是写实拷贝机制,当多个进程通过 fork
系统调用创建子进程时,父进程和子进程会共享同一块物理内存页面,操作系统不会立即复制内存页;当子进程或父进程尝试修改共享内存页中的数据时,系统会检测到写操作,此时才会触发真正的复制机制,系统为需要修改的内存页创建一个新的副本,并将该副本映射到当前进程的地址空间中!
③创建多进程
运行结果:
4-3 一个函数fork,怎么会有两个返回值?
fork函数在执行return之前,要完成创建子进程的过程,这个主体功能包括从父进程拷贝一个task_struct、调整新task_struct的部分属性,将该struct链接到进程列表中,此时子进程已经创建完毕!
那么在执行return语句的时候就已经有父子两个进程了,根据进程数据的独立性,此时两个进程分别创建返回值的临时变量,因此返回值变量实际是两个不同进程中的独立变量,因此可以返回不同的值;
注意:fork之后,父子进程谁先运行,是由OS的调度器自主决定的!
5.操作系统上看进程状态
广义上讲,进程的状态是这样的:
5-1 广义的进程状态
补充知识:
1.时间片:
Linux/Windows民用级别的操作系统都是分时系统,调度任务追求公平。
CPU执行进程的代码,不是把进程代码执行完毕,才开始执行下一个,而是给每个进程分配一个时间片,基于时间片进行轮转调度!
2.并行和并发:
并发:多个进程在同一个cpu下采用进程切换的方式,在一段时间内,让多个进程都得以推进开发
并行:多个进程在多个cpu下,分别同时进行运行;
3.对运行、阻塞、挂起的理解
(1)运行:一台电脑一定有多个进程在运行,也就是内存中会有多个task_struct,这些结构体会被放到运行队列中等待cpu调度,那么, 当一个进程被创建出来,被放到CPU的运行队列中后,即便没有真正运行起来,我们也可以说是运行状态了!
(2)阻塞:
因为OS对各外设的管理是基于“先描述,再组织”的,每一个外设都可以用一个struct来描述属性,再用指针链接下一个外设struct,所以对外设的管理本质上是内存中对链表的增删查改
当运行到某一个进程时,如果遇到像IO操作这样依赖外设的情况时,为了保证其它进程能够正常如期排到,会将该进程出队列,转移到对应外设的队列中等待,这个过程叫做阻塞。当外设检测到IO执行完毕,会通知操作系统将该进程再入到运行队列中。
(3)挂起:如果内存严重不足,由于在阻塞时进程的数据仍占据内存,OS会将处于阻塞状态的进程的数据全部换出到磁盘的swap分区,当阻塞状态解除时再换入回运行队列。
5-2.linux进程的状态
1.R:运行状态,进程正在运行或准备好运行(在运行队列中等待 CPU 时间片)
2.S:可中断睡眠(浅睡眠):进程正在等待某个事件,但可以被信号唤醒。例如,调用 sleep()
或 read()
等系统调用时,进程会进入这种状态。
3.D:不可中断睡眠(深睡眠):进程正在等待某个事件,但不能被信号唤醒。例如,进程正在等待磁盘 I/O 操作完成时,会进入这种状态。
4.T:停止状态:进程做法非法但不致命的操作,被OS暂停了;
5.t:追踪停止状态:当进程被追踪的时候,断点停下;
6.Z:僵尸状态:进程已经完成,但父进程尚未读取其状态信息。这种状态的进程不能被唤醒,也不能被调度器调度。
7.X:死亡状态:进程已经完全终止,所有资源都被释放,进程的 PCB 也被移除。这种状态的进程不会出现在进程列表中。
8.P:挂起状态:进程被挂起并等待某些条件满足后继续执行。这种状态的进程不会被调度器调度
5-3 理解僵尸进程、孤儿进程
1.进程退出后,会把退出信息保存在task_struct中。
僵尸进程是指已经完成执行完毕(代码数据被释放了),但其父进程尚未读取其状态信息的进程。在这种状态下,进程的资源(如文件描述符、内存等)已经被释放,但进程的 PCB(进程控制块)仍然保留在系统中,直到父进程读取其状态信息。
问题:虽然僵尸进程不会占用 CPU 时间,但它的 PCB 仍然占用系统资源,若不回收,就一直僵尸,会造成内存泄漏!
解决方式:父进程应该在子进程结束时调用 wait()
或 waitpid()
来读取子进程的状态信息,从而释放子进程的 PCB。
2. 如果子进程还存在,父进程却挂掉了,那么子进程会被系统进程“领养”,这就是孤儿进程;
更多推荐
所有评论(0)