一、计算机结构
在要了解进程之前,需要先对计算的组成有一个大概的了解,这对于我们理解进程,有着很大的帮助。
在现在的计算机中,大多采用冯诺伊曼体系结构,在冯诺伊曼中,主要有以下几个部件。
这里需要注意一下几点:
- 这⾥里的存储器指的是内存
- 不考虑缓存情况,这⾥里的CPU能且只能对内存进⾏行读写,不能访问外设(输⼊入或输出设备)
- 外设(输⼊入或输出设备)要输⼊入或者输出数据,也只能写⼊入内存或者从内存中读取。一句话,所有设备都只能直接和内存打交道。
目前,在我们的世界中,计算机都是由一个个的硬件组成的
- 输⼊入单元:包括键盘, ⿏鼠标,扫描仪, 写板等
- 中央处理器(CPU):含有运算器和控制器等
- 输出单元:显⽰示器,打印机等
二、操作体统的理解
1、概念
任何计算机系统都包含⼀一个基本的程序集合,称为操作系统(OS)。笼统的理解,操作系统包括:
内核(进程管理,内存管理,⽂文件管理,驱动管理)
其他程序(例如函数库,shell程序等等)
2、操作系统的目标
操作系统(Operator System)是控制应用程序执行的程序,并充当应用程序和计算机硬件之间的接口,主要有以下三个目标:
(1)方便
(2)有效
(3)扩展功能
3、设计操作系统的目的
与硬件交互,管理所有的软硬件资源
为⽤用户程序(应⽤用程序)提供⼀一个良好的执⾏行环境
通过上面即可得出,操作系统就是一款“搞管理”的软件,对下,通过驱动程序管理硬件设备,对上,通过系统调用接口实现各种命令与软件的调用。
总结起来就是:计算机管理硬件
1. 描述起来,用struct结构体
2. 组织起来,用链表或其他高效的数据结构
三、进程
1、基本概念
课本概念:程序的⼀一个执⾏行实例,正在执⾏行的程序等
内核观点:担当分配系统资源(CPU时间,内存)的实体。
其他定义:
a正在执行的程序
b正在计算机上执行的程序实例
c能分配给处理器,并且由处理器执行的实体
d具有以下特征的活动单元:一组指令序列的执行,一个当前状态和相关的系统资源集
2、描述进程—–PCB
- 进程信息被放在一个叫做进程控制块的数据结构中,可以理解为进程属性的集合。
- 课本上称之为PCB(process control block),Linux操作系统下的PCB是: task_struct
- 在Linux中描述进程的结构体叫做task_struct。
- task_struct是Linux内核的⼀一种数据结构,它会被装载到RAM(内存)⾥里并且包含着进程的信息。
3、task_ struct内容分类
在进程执行时,任意给定一个时间,进程都可以唯一的被表征为以下元素。
- 标示符: 描述本进程的唯一标示符,⽤用来区别其他进程。
- 状态: 任务状态,退出代码,退出信号等。
- 优先级: 相对于其他进程的优先级。
- 程序计数器: 程序中即将被执行的下一条指令的地址。
- 内存指针: 包括程序代码和进程相关数据的指针,还有和其他进程共享的内存块的指针
- 上下文数据: 进程执行时处理器的寄存器中的数据
- I/O状态信息: 包括显⽰示的I/O请求,分配给进程的I/O设备和被进程使用的文件列表。
- 记账信息: 可能包括处理器时间总和,使用的时钟数总和,时间限制,记账号等。
- 其他信息
进程控制块如下所示:
上面的进程控制块由操作系统创建和管理。进程控制块是操作系统能够支持多进程和提供多处理的关键工具。
当进程被中断时,操作系统会把程序计数器和处理寄存器(上下文数据)保存到进程控制块的相应位置,进程状态也被改变为其他的值。
因此,可以说进程是由程序代码和相关数据还有进程控制块组成。
4、组织进程
所有运行在系统里的进程都以task_struct链表的形式存在内核⾥里。
5、查看进程
(1)在根目录下,在/proc这个文件中可以查看
(2)利用ps aux
利用ps aux查看进程,可以知道所有进程的各种信息。
(3)查看系统的pid
#include <sys/types.h>
#include <unistd.h>
pid_t getpid(void);//获得子进程的进程标识符
pid_t getppid(void);//获得父进程的进程标识符
6、进程的创建
当一个新进程添加到那些正在被管理的进程集合中去时,操作系统需要建立用于管理该进程的数据结构,并在内存中给他分配地址空间。
一个应用程序进程可以产生另一个进程,以接收应用程序产生的数据,并将数据组织成合适以后分析的格式。新进程与应用程序并行的运行,并得到新的数据时被激活。
当一个进程派生另一个进程时,前一个称作父进程,被派生的进程称作为子进程。
创建父进程fork()
#include <unistd.h>
pid_t fork(void);
//返回值pid_t类似于无符号的整形,且返回值有两个,子进程创建失败,返回-1,创建成功,子进程返回0,父进程返回大于0的值即可
7、进程终止
向操作系统发送某种信号,从而终止正在运行或者睡眠的进程,操作系统中提供以下信号:
通过上面可知,9号信号可以杀死一个进程,如下所示:
8、进程的五种状态
说明:
运行态:该进程正在执行,例如,假如计算机只有一个处理器,因此一次最多只有一个进程处于运行态
就绪态:进程做好准备,只要有机会就开始执行
阻塞/等待态:进程在某些事件发生前不能执行,例如I/O操作完成
新建态:刚刚创建的进程,操作系统还没有把他加入到可执行进程组中。通常是进程控制块已经创建但还没有加载到内存中的新进程。
退出态:操作系统从可执行进程组中释放出的进程,或者是因为他自身停止 ,或者是因为某种原因被取消。
四、进程状态
为了弄明白正在运行的进程是什么意思,我们需要知道进程的不同状态。一个进程可以有几个状态(在Linux内核里,进程有时候也叫做任务)。 下面的状态在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 */
};
几种状态的分析:
* R运行状态(running): 并不意味着进程一定在运⾏行中,它表明进程要么是在运⾏行中要么在运行队列里。
* S睡眠状态(sleeping): 意味着进程在等待事件完成(这⾥里的睡眠有时候也叫做可中断睡眠(interruptible sleep))。进程任然在执行代码,只不过这里的代码是睡眠状态的代码,这种睡眠状态可以响应常规的控制信息和状态信息。
* D磁盘休眠状态(Disk sleep)有时候也叫不可中断睡眠状态(uninterruptible sleep),在这个状态的进程通常会等待IO的结束。这是一种特殊的睡眠状态,且外界因素不可将其唤醒,这个状态不可以通过发送信号将其杀死,通常在I/O时发生。好比你永远无法叫醒一个装睡的人。
* T停⽌止状态(stopped): 可以通过发送 SIGSTOP 信号给进程来停止(T)进程。这个被暂停的进 程可以通过发送SIGCONT 信号让进程继续运行。
* X死亡状态(dead):这个状态只是一个返回状态,你不会在任务列表里看到这个状态。
2、进程状态的修改
编写一个死循环的代码,从而利用ps命令查看进程的状态,如下所示:
从上面我们可以知道,通过向进程发相应的信号,从而可以改变进程的状态,现在我们将进程的状态改为僵尸状态,首先先了解一下什么事僵尸状态以及僵尸状态的危害
程序退出的几种情况
子进程正常退出,代码执行完成且结果正确
子进程正常退出,代码执行完成但是结果不正确
子进程异常退出。
因此父进程就需要知道子进程的任务到底完成的如何,因此就需要回收子进程的状态,如果长时间不进行回收,则子进程就会形成僵尸状态。
僵尸状态的形成:
* 僵死状态(Zombies)是一个比较特殊的状态。当进程退出并且父进程(使用wait()系统调用,后面讲)没有读取到子进程退出的返回代码时就会产生僵死(尸)进程
* 僵死进程会以终止状态保持在进程表中,并且会一直在等待父进程读取退出状态代码。所以,只要子进程退出,父进程还在运行,但父进程没有读取子进程状态,子进程进入Z状态。
僵尸状态的危害:
* 进程的退出状态必须被维持下去,因为他要告诉关心它的进程(父进程),你交给我的任务,我办的怎么样了。可父进程如果一直不读取,那子进程就一直处于Z状态
* 维护退出状态本⾝身就是要用数据维护,也属于进程基本信息,所以保存在task_struct(PCB)中,换句话说,Z状态一直不退出,PCB⼀一直都要维护
* 那一个父进程创建了很多子进程,就是不回收,是不是就会造成内存资源的浪费?是的!因为数据结构对象本⾝身就要占用内存,想想C中定义⼀一个结构体变量(对象),是要在内存的某个位置进行开辟空间!
* 因此,总结上面可知,僵尸进程如果一直不被回收,则就会发生内存泄漏的问题。
创建僵尸进程,子进程退出,但是父进程还没有回收资源,则就会形成僵尸进程。如下所示:
可以通过让子进程先退出的形式形成其他状态,当然,也可以通过向指定进程发送信号的形式从而形成僵尸进程,如下所示:
最后,在这里,还要讨论一下有关睡眠态与暂停态的区别:
睡眠态:睡眠,意味着他还执行着与其相关的进程,而对于暂停态,就意味着,什么都不干,就只是停到那块,什么也不干,等着发送相关的信号,将其变为之前的状态。
至此,值得关注的进程状态全部讲解完成,下面来认识另一种进程
现在让我们在了解一个带有负能量的进程,孤儿进程
孤儿进程的形成
* 父进程如果提前退出,那么子进程后退出,进入Z之后,那该如何处理呢?
* 父进程先退出,子进程就称之为“孤儿进程”
* 孤儿进程被1号init进程领养,当然要有init进程回收喽。这里的1号进程一般为操作系统。
* 如果孤儿进程不被回收,则最后也容易造成内存泄漏。
下面,让我们来演示一下孤儿进程,当然,在这里我们看不到其状态,只能通过子进程被1号进程回收,从而验证,如下所示:
上面,介绍完了进程的各种状态以及进程状态的修改,这是我们在今后的学习有着至关重要的一步,因此,希望上面的内容可以帮助到大家。进程,是运行在操作系统上的程序,操作系统上当然不止一个程序,各个程序之间的运行,需要占据CPU的资源,因此,各个进程之间就有竞争的关系,但是有的进程在整个程序中又有着至关重要的作用,因此,下面我们将介绍另一个概念,进程优先级。
五、进程优先级
1、基本概念
* cpu资源分配的先后顺序,就是指进程的优先权(priority)。
* 优先权高的进程有优先执⾏、行权利。配置进程优先权对多任务环境的linux很有⽤用,可以改善系统性能。
* 还可以把进程运行到指定的CPU上,这样一来,把不重要的进程安排到某个CPU,可以大大改善系统整体性能。这种情况对于多核的情况下。
2、查看进程的优先级
根据上面,我们可以注意到以上画出来的信息是我们从来都没有见过的,现在,我们来解释一下这些名词:
* UID : 代表执行者的身份
* PID : 代表这个进程的代号
* PPID :代表这个进程是由哪个进程发展衍生而来的,亦即父进程的代号
* PRI :代表这个进程可被执行的优先级,其值越小越早被执行
* NI :代表这个进程的nice值
从上面的图我们也可以看出,PRI与NI 的值都默认为80和0,因此,一个系统默认的优先级即为80,有了这些值,当然就要进行修改,现在,让我们来看一下修改的方式:
PRI and NI
* PRI值越小越快被执行,那么加入nice值后,将会使得PRI变为:PRI(new)=PRI(old)+nice
* 当nice值为负值的时候,那么该程序将会优先级值将变小,即其优先级会变高,则其越快被执行
总结:调整进程优先级,在Linux下,就是调整进程nice值
nice其取值范围是-20⾄至19,一共40个级别。
需要强调一点的是,进程的nice值不是进程的优先级,他们不是一个概念,但是进程nice值会影响到进程的优先级变化。可以理解nice值是进程优先级的修正修正数据。
总结:
* 启动进程前调整: nice
开始执行程序就指定nice值: nice -n -5 ./test
* 调整已存在进程的nice: renice
renice -5 -p 5200 //PID为5200的进程nice设为-5
* 用top命令更改已存在进程的nice:
top
进⼊入top后按“r”–>输⼊入进程PID–>输⼊入nice值
对于这些修改优先级的方法,这里就不一一演示了,大家有兴趣的话,可以自己来演示。
有关进程的基础知识,大概就这么多了,下一次,我们将对进程做进一步的分析,希望大家可以监督。
只有不停的奔跑,才能不停留在原地!!!