哈喽大家好,我是 鹿九丸 \color{red}{鹿九丸} 鹿九丸,今天给大家带来的是Linux进程概念(上)。
如果大家在看我的博客的过程中或者学习的过程中以及在学习方向上有什么问题或者想跟我交流的话可以加我的企鹅号: 2361038962 \color{red}{2361038962} 2361038962,或者寄邮件到相应的邮箱里: 2361038962 @ q q . c o m \color{red}{[email protected]} 2361038962@qq.com,我会尽量帮大家进行解答!
输入:键盘、话筒、摄像头、磁盘、网卡…
输出:显示器、音响、磁盘、网卡…
(运算器 + 控制器)[CPU]:
存储器:内存
问:为什么要有内存?
答:技术角度:cpu的运算速度 > 寄存器的速度 > L1~L3 Cache > 内存 >> 外设(磁盘)>> 光盘磁带
从数据角度,外设几乎不和CPU直接进行交互,直接和内存直接交互,CPU也同样如此。
成本角度:寄存器 >> 内存 >> 磁盘(外设)
注意:几乎所有的硬件,只能被动的完成某种功能,不能主动的完成某种功能,一般都是要配合软件完成的(OS + CPU)。
任何计算机系统都包含一个基本的程序集合,称为操作系统(OS)。笼统的理解,操作系统包括:
- 内核(进程管理,内存管理,文件管理,驱动管理)
- 其他程序(例如函数库,shell程序等等)
- 与硬件交互,管理所有的软硬件资源
- 为用户程序(应用程序)提供一个良好的执行环境
- 在整个计算机软硬件架构中,操作系统的定位是:一款纯正的“搞管理”的软件
计算机管理硬件
- 描述起来,用struct结构体
- 组织起来,用链表或其他高效的数据结构
- 在开发角度,操作系统对外会表现为一个整体,但是会暴露自己的部分接口,供上层开发使用,这部分 由操作系统提供的接口,叫做系统调用。
- 系统调用在使用上,功能比较基础,对用户的要求相对也比较高,所以,有心的开发者可以对部分系统 调用进行适度封装,从而形成库,有了库,就很有利于更上层用户或者开发者进行二次开发。
- 课本概念:程序的一个执行实例,正在执行的程序等
- 内核观点:担当分配系统资源(CPU时间,内存)的实体。
- 进程信息被放在一个叫做进程控制块的数据结构中,可以理解为进程属性的集合。
- 课本上称之为PCB(process control block),Linux操作系统下的PCB是:task_struct
- 在Linux中描述进程的结构体叫做task_struct。
- task_struct是Linux内核的一种数据结构,它会被装载到RAM(内存)里并且包含着进程的信息。
- 标示符: 描述本进程的唯一标示符,用来区别其他进程。
- 状态: 任务状态,退出代码,退出信号等。
- 优先级: 相对于其他进程的优先级。
- 程序计数器: 程序中即将被执行的下一条指令的地址。
- 内存指针: 包括程序代码和进程相关数据的指针,还有和其他进程共享的内存块的指针
- 上下文数据: 进程执行时处理器的寄存器中的数据[休学例子,要加图CPU,寄存器]。
- I/O状态信息: 包括显示的I/O请求,分配给进程的I/O设备和被进程使用的文件列表。
- 记账信息: 可能包括处理器时间总和,使用的时钟数总和,时间限制,记账号等。
- 其他信息
每个进程在系统中,都会存在一个唯一的标识符。这个标识就是PID(process ID)
注意:PID每次开启新进程都会随机分配一个PID。
- 可以在内核源代码里找到它。所有运行在系统里的进程都以task_struct链表的形式存在内核里。
进程的信息可以通过 /proc
系统文件夹查看(pro的全称是process)
注意:proc是内存文件系统,存放当前系统实时的进程信息。
- 如:要获取PID为1的进程信息,你需要查看
/proc/1
这个文件夹。
注意:上面这些蓝色的数字就是进程的PID。
- 大多数进程信息同样可以使用top和ps这些用户级工具来获取
首先先编辑一个.c代码:
然后运行:
使用
ps ajx
命令即可查看进程(all job,x是以特定格式进行显示)
使用
ps ajx | grep 'mytest'
查看与mytest
相关的进程:注意:我们常常使用的
ls、pwd、touch、grep、chgrp、chown、mkdir、rm
等命令在启动后也都是一个个进程,这些二进制可执行文件在/usr/bin
目录下,我们使用下面的指令可以进行查看,ls /usr/bin/*
使用
ps ajx | grep 'mytest' | grep -v grep
我们可以查看待用mytest
关键词同时又不带有grep
关键词的进程:
问:如何查看进程信息和进程信息代表的意义?
答:使用
ps ajx | head -1
可以查看不同列的进程信息代表的意义:
使用
ps ajx | head -1 && ps ajx | grep 'mytest' | grep -v grep
注意:&&是逻辑与,其意义是前面的指令执行成功了,再实行后面的指令。
此时根据PID在proc目录中进行查看:
或者:
使用
ls /proc/31745 -al
可以查看进程的详细信息:cwd(current work director):进程当前的工作路径。
exe后面对应的是可执行程序的磁盘文件。
- 进程id(PID)
- 父进程id(PPID)
使用
man getpid
来查看getpid介绍
使用举例:
运行:
使用举例:
运行:
问:为什么每次创建进程时,当前进程的PID每次都会改变,但是PPID却没有发生改变?
答:因为几乎我们在命令行上所执行的所有的指令(cmd),都是bash进程的子进程。
运行 man fork 认识fork
man 2 fork
fork有两个返回值
代码:
运行结果:
代码:
执行结果:
问:为什么会出现这种情况?
答:fork之后,父进程和子进程会共享代码,一般都会执行后续的代码,这就是为什么printf会打印两次的问题。
fork之后,父进程和子进程返回值不同,可以通过不通的返回值,判断,让父子执行不同的代码块。
问:为什么fork会给父进程返回子进程的PID,给子进程返回0?
答:因为父进程必须有标识子进程的PID来方便对子进程进行管理,所以fork之后会给父进程返回子进程的PID。
子进程最重要的是知道自己被创建成功了,因为子进程找父进程成本非常低(getppid())。
问:为什么fork函数会返回两次,有两个返回值?
注意:for()之前的代码,在子进程中将不会继续执行。
父子进程代码共享,数据各自开辟空间,私有一份(采用写时拷贝)
下面的状态在kernel源代码里定义:
/*
* 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 */
};
注意:进程的状态定义在进程的task_struct中。
R运行状态(running): 并不意味着进程一定在运行中,它表明进程要么是在运行中要么在运行队列里。 (操作系统中叫执行)
问:运行态是进程在CPU上运行,还是进程只要在运行队列中叫作运行态?
运行态表示当前进程的task_struct在运行队列runqueue中,已经准备好了,可以随时被调度到CPU中进行执行。(参考分时操作系统)
S睡眠状态(sleeping): 意味着进程在等待事件完成(这里的睡眠有时候也叫做可中断睡眠 (interruptible sleep))。(操作系统中叫阻塞)
定义:当进程访问某些资源(磁盘网卡),该资源如果暂时没有准备好,或者正在给其它进程提供服务,此时:1. 当前进程要从runqueue中移除 2. 将当前进程放入对应设备的描述结构体中的等待队列。此时进程就处于阻塞状态。
注意:处于阻塞状态的进程的代码并没有被执行。
D磁盘休眠状态(Disk sleep)有时候也叫不可中断睡眠状态(uninterruptible sleep),在这个状态的进程通常会等待IO的结束。 (操作系统中叫阻塞)
例如下面代码举例:
struct disk_div { //磁盘属性 task_struct *wait_queue; }
在等待队列中的进程就处于阻塞状态。
问:S和D有什么区别吗?
答:S可以被中断,即可以被操作系统强制回收,但是D只能等待程序自己结束或者醒来,操作系统无法强制回收。
T停止状态(stopped): 可以通过发送 SIGSTOP 信号给进程来停止(T)进程。这个被暂停的进程可 以通过发送 SIGCONT 信号让进程继续运行。 (操作系统中叫终止态)
问:这个进程已经被释放,就叫终止态?还是该进程还在,只不过永远不运行了,随时等待被释放?
答:该进程还在,只不过永远都不运行了,这种状态叫作终止态。为什么这种状态才叫作终止态?因为释放是要花费时间的,有时候操作系统很忙,所以没法立即回收我们的进程以及进程所占用的资源,回收进程和进程占用的资源与修改task_struct的状态位相比,显然前者的开销是要更小一些的。
问:如何使程序处于暂停状态?
答:使用
kill -19 PID
命令就可以使程序处于暂停状态。问:如何使程序继续?
答:使用
kill -18 PID
命令就可以使程序继续。X死亡状态(dead):这个状态只是一个返回状态,你不会在任务列表里看到这个状态。
运行:R
终止:Z和X
阻塞:S或者D
挂起:S或者T
挂起状态:
ps aux / ps axj 命令
使用举例:
process.c文件:
运行之后查看进程状态:
问:此时进程的状态是S,说明程序处于休眠状态或者阻塞状态,为什么此时的程序处于休眠状态而不是运行状态?
答:因为CPU执行的速度很快,大部分时间,进程在等待外设即输出设备,所以大部分时间是阻塞状态
对源程序进行下面的改变,进程的状态就会发生改变:
注意:在上图中的就绪和执行状态在操作系统中都对应的是R状态。
- 僵死状态(Zombies)是一个比较特殊的状态。当进程退出并且父进程(使用wait()系统调用,后面讲) 没有读取到子进程退出的返回代码时就会产生僵死(尸)进程
- 僵死进程会以终止状态保持在进程表中,并且会一直在等待父进程读取退出状态代码。
- 所以,只要子进程退出,父进程还在运行,但父进程没有读取子进程状态,子进程进入Z状态
模拟僵尸进程:
代码:
问:长时间的保持僵尸状态会出现什么问题?
答:如果没有人回收僵尸子进程,该状态会一直维护,并且该进程的相关资源(task_struct)也不会被释放,进而出现内存泄漏问题。
- 父进程如果提前退出,那么子进程后退出,进入Z之后,那该如何处理呢?
- 父进程先退出,子进程就称之为“孤儿进程”
- 孤儿进程被1号init进程领养,当然要有init进程回收喽。
process.c代码:
执行结果:
最终只剩下一个子进程。
问:S+和S状态有什么区别?
答:S+是前台进程,可以通过ctrl + c退出,但是S状态不可以。当父进程尚未退出时,两个进程的状态都是S+ 状态,但是当父进程退出后,子进程变成了S状态。
问:父进程退出的时候为什么是S状态而不是Z状态?
答:因为父进程的父进程是bash,父进程被bash回收了。
问:父进程退出的时候,子进程为什么没有被回收?
答:因为在我们的代码中,我们没有写父进程回收子进程的的代码,所以子进程没有被回收。
问:父进程被回收后,为什么子进程的父进程发生了改变?
答:如果此时的父进程没有发生改变的话,就没有父进程来对子进程进行管理了。所以如果父进程提前退出,子进程还在,子进程就会被1号进程领养。此时的子进程就是孤儿进程。
问:如何退出此时的孤儿进程?
答:当父进程退出后,子进程的状态变成了S状态,此时我们发现子进程无法通过ctrl + C退出,此时我们只能通过
kill -9 PID
使进程退出。问:什么是1号进程?
答:1号进程就是操作系统。
T是常规的暂停,t是进程被调试的时候遇到断点时所处的状态。
- cpu资源分配的先后顺序,就是指进程的优先权(priority)。
- 优先权高的进程有优先执行权利。配置进程优先权对多任务环境的linux很有用,可以改善系统性能。
- 还可以把进程运行到指定的CPU上,这样一来,把不重要的进程安排到某个CPU,可以大大改善系统整 体性能。
在linux或者unix系统中,用ps –l命令则会类似输出以下几个内容:
- UID : 代表执行者的身份
- PID : 代表这个进程的代号
- PPID :代表这个进程是由哪个进程发展衍生而来的,亦即父进程的代号
- PRI :代表这个进程可被执行的优先级,其值越小越早被执行(priority)
- NI :代表这个进程的nice值(nice)
- PRI即进程的优先级,就是程序被CPU执行的先后顺序,此值越小,进程的优先级别越高
- NI表示进程可被执行的优先级的修正数值
- PRI值越小越快被执行,那么加入nice值后,将会使得PRI变为:PRI(new)=PRI(old)+nice(注意:每次设置,此处都默认PRI都是80)
- 这样,当nice值为负值的时候,那么该程序将会优先级值将变小,即其优先级会变高,则其越快被执行
- 所以,调整进程优先级,在Linux下,就是调整进程nice值 nice其取值范围是-20至19,一共40个级别([60 , 99])。
注意:进程的nice值不是进程的优先级,他们不是一个概念,但是进程nice值会影响到进 程的优先级变化。 可以理解为:nice值是进程优先级的修正修正数据
- 竞争性: 系统进程数目众多,而CPU资源只有少量,甚至1个,所以进程之间是具有竞争属性的。为了高 效完成任务,更合理竞争相关资源,便具有了优先级
- 独立性: 多进程运行,需要独享各种资源,多进程运行期间互不干扰
- 并行: 多个进程在多个CPU下分别,同时进行运行,这称之为并行
- 并发: 多个进程在一个CPU下采用进程切换的方式,在一段时间之内,让多个进程都得以推进,称之为 并发
问:Linux内部是如何实现的?
答:不同优先级会形成不同的队列,不同优先级的进程会放入相应优先级所对应的队列中。