目录
基本概念
课本概念:程序的一个执行实例,正在执行的程序等
内核观点:担当分配系统资源(CPU时间,内存)的实体。
进程不是运行中的程序,也不是在内存中的程序。
当程序运行出现a.exe会被存入磁盘中, 然后操作系统将它加载,拷贝到内存中,内存中存的这个东西就是进程。
进程描述-PCB
进程信息被放在一个叫做进程控制块的数据结构中,可以理解为进程属性的集合。
课本上称之为PCB(process control block),Linux操作系统下的PCB是: task_struct
系统当中可以同时存在大量进程,使用命令ps aux便可以显示系统当中存在的进程。
当我们电脑开机的时候,启动的第一个程序就是操作系统OS,操作系统是做管理工作的,也包括了进程管理,对于进程管理:先描述,后组织。课本上称之为PCB(process control block),Linux操作系统下的PCB是: task_struct。
进程信息被放在一个叫做进程控制块的数据结构中,可以理解为进程属性的集合。
操作系统将每一个进程都进行描述,形成了一个个的进程控制块(PCB),并将这些PCB以双链表的形式组织起来。
操作系统对进程的管理就变成了对双链表的管理。创建新进程就对双链表增加PCB,退出进程就删除PCB。
task_struct-PCB的一种
1.在Linux中描述进程的结构体叫做task_struct。
2.task_struct是Linux内核的一种数据结构,它会被装载到RAM(内存)里并且包含着进程的信息。
task_struct内容分类
- 指示符: 描述本进程的唯一标示符,用来区别其他进程。
- 状态: 任务状态,退出代码,退出信号等。
- 优先级: 相对于其他进程的优先级。
- 程序计数器: 程序中即将被执行的下一条指令的地址。
- 内存指针: 包括程序代码和进程相关数据的指针,还有和其他进程共享的内存块的指针
- 上下文数据: 进程执行时处理器的寄存器中的数据[休学例子,要加图CPU,寄存器]。
- I/O状态信息: 包括显示的I/O请求,分配给进程的I/O设备和被进程使用的文件列表。
- 记账信息: 可能包括处理器时间总和,使用的时钟数总和,时间限制,记账号等。
- 其他信息
查看进程
通过系统目录查看
通过根目录下的proc系统文件夹查看:
这些数字是进程的PID,我们如果想看PID为1的进程的进程信息,查看名字为1的文件夹即可。
通过ps命令查看
指令:ps aux
ps命令与grep命令搭配使用,即可只显示某一进程的信息。
指令:ps aux | head -1 && ps aux | grep proc | grep -v grep
通过系统调用获取进程的PID和PPID
通过使用系统调用函数,getpid和getppid即可分别获取进程的PID和PPID。
通过系统调用创建进程- fork初始
若是代码当中没有fork函数,我们都知道代码的运行结果就是循环打印该进程的PID和PPID。而加入了fork函数后,代码运行结果如下:
第一行是该进程的PID和PPID,第二行是fork创建子进程的PID和PPID 。
我们知道加载到内存当中的代码和数据是属于父进程的,那么fork函数创建的子进程的代码和数据又从何而来呢?
fork创建了子进程,fork被调用之前的代码被父进程执行,而fork之后的代码,默认情况下父进程和子进程都执行,但是,父子进程虽然代码共享,但是父子进程的数据各自开辟空间。
使用if进行分流
fork函数创建出来的子进程与其父进程共同使用一份代码,但我们如果真的让父子进程做相同的事情,那么创建子进程就没有什么意义了。
实际上,在fork之后我们通常使用if语句进行分流,即让父进程和子进程做不同的事。
fork函数的返回值:
1、如果子进程创建成功,在父进程中返回子进程的PID,而在子进程中返回0。
2、如果子进程创建失败,则在父进程中返回 -1。
对fork的理解
Linux进程状态
一个进程从创建到结束期间,都需要cpu执行,但有时分不到,然后就有了进程的各种状态,于是就有了进程状态这一概念。
为了弄明白正在运行的进程是什么意思,我们需要知道进程的不同状态。一个进程可以有几个状态(在
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 *task_state_array[] = {
"R (running)", /* 0*/
"S (sleeping)", /* 1*/
"D (disk sleep)", /* 2*/
"T (stopped)", /* 4*/
"T (tracing stop)", /* 8*/
"Z (zombie)", /* 16*/
"X (dead)" /* 32*/
};
注意:进程的状态是保存在进程的PCB里面的。
指令:ps aux 或者 ps ajx 都可以查看进程当前的状态。
运行状态-R
一个进程处于运行状态(running),并不意味着进程一定处于运行当中,运行状态表明一个进程要么在运行中,要么在运行队列里。也就是说,可以同时存在多个R状态的进程。
注意: 所有处于运行状态,即可被调度的进程,都被放到运行队列当中,当操作系统需要切换进程运行时,就直接在运行队列中选取进程运行。
浅度睡眠状态-S
一个进程处于浅度睡眠状态(sleeping),意味着该进程正在等待某件事情的完成,处于浅度睡眠状态的进程随时可以被唤醒,也可以被杀掉(这里的睡眠有时候也可叫做可中断睡眠(interruptible sleep))。
eg:
指令:ps ajx | head -1 && ps ajx | grep 文件名 | grep -v grep 查看进程状态
浅度睡眠状态是可以使用kill -9 PID命令杀掉的。
深度睡眠状态-D
一个进程处于深度睡眠状态(disk sleep),表示该进程不会被杀掉,即便是操作系统也不行,只有该进程自动唤醒才可以恢复。该状态有时候也叫不可中断睡眠状态(uninterruptible sleep),处于这个状态的进程通常会等待IO的结束。
例如,某一进程要求对磁盘进行写入操作,那么在磁盘进行写入期间,该进程就处于深度睡眠状态,是不会被杀掉的,因为该进程需要等待磁盘的回复(是否写入成功)以做出相应的应答。(磁盘休眠状态)
暂停状态-T
在Linux当中,我们可以通过发送SIGSTOP信号使进程进入暂停状态(stopped),发送SIGCONT信号可以让处于暂停状态的进程继续运行。
eg:对一个进程进行 kill -SIGSTOP PID让该进程进行暂停状态
发送 kill -SIGCONT PID 就可以继续运行了。
僵尸状态-Z
当一个进程将要退出的时候,在系统层面,该进程曾经申请的资源并不是立即被释放,而是要暂时存储一段时间,以供操作系统或是其父进程进行读取,如果退出信息一直未被读取,则相关数据是不会被释放掉的,一个进程若是正在等待其退出信息被读取,那么我们称该进程处于僵尸状态(zombie)。
首先,僵尸状态的存在是必要的,因为进程被创建的目的就是完成某项任务,那么当任务完成的时候,调用方是应该知道任务的完成情况的,所以必须存在僵尸状态,使得调用方得知任务的完成情况,以便进行相应的后续操作。
eg:在子进程运行五秒后子进程退出,父进程继续,看看子进程会不会变成僵尸进程!!
监控指令:while :; do ps axj | head -1 && ps axj | grep 文件名 | grep -v grep;echo “######################”;sleep 1;done
理解及僵尸进程的危害:
死亡状态-X
死亡状态只是一个返回状态,当一个进程的退出信息被读取后,该进程所申请的资源就会立即被释放,该进程也就不存在了,所以你不会在任务列表当中看到死亡状态(dead)。
孤儿进程
在Linux当中的进程关系大多数是父子关系,若父进程先退出,子进程就称之为“孤儿进程。
若是一直不处理孤儿进程的退出信息,那么孤儿进程就会一直占用资源,此时就会造成内存泄漏。因此,当出现孤儿进程的时候,孤儿进程会被1号init进程领养,此后当孤儿进程进入僵尸状态时就由init进程进行处理回收。
进程优先级
基本概念
什么是优先级?
优先级实际上就是获取某种资源的先后顺序,而进程优先级实际上就是进程获取CPU资源分配的先后顺序,就是指进程的优先权(priority),优先权高的进程有优先执行的权力。
优先级存在的原因?
优先级存在的主要原因就是资源是有限的,而存在进程优先级的主要原因就是CPU资源是有限的,一个CPU一次只能跑一个进程,而进程是可以有多个的,所以需要存在进程优先级,来确定进程获取CPU资源的先后顺序。
查看系统进程
在Linux或者Unix操作系统中,用ps -l命令会类似输出以下几个内容:
列出的信息当中有几个重要的信息,如下:
- UID:代表执行者的身份。
- PID:代表这个进程的代号。
- PPID:代表这个进程是由哪个进程发展衍生而来的,亦即父进程的代号。
- PRI:代表这个进程可被执行的优先级,其值越小越早被执行。
- NI:代表这个进程的nice值。
PRI与NI
- PRI代表进程的优先级(priority),通俗点说就是进程被CPU执行的先后顺序,该值越小进程的优先级别越高。
- NI代表的是nice值,其表示进程可被执行的优先级的修正数值。
- PRI值越小越快被执行,当加入nice值后,将会使得PRI变为:PRI(new) = PRI(old) + NI。
- 若NI值为负值,那么该进程的PRI将变小,即其优先级会变高。
- 调整进程优先级,在Linux下,就是调整进程的nice值。
- NI的取值范围是-20至19,一共40个级别。
注意: 在Linux操作系统当中,PRI(old)默认为80,即PRI = 80 + NI。
查看进程优先级信息
指令:ps -al
注意: 在Linux操作系统中,初始进程一般优先级PRI默认为80,NI默认为0。
通过top命令更改进程的nice值
top命令就相当于Windows操作系统中的任务管理器,它能够动态实时的显示系统当中进程的资源占用情况。
使用top命令后按“r”键,会要求你输入待调整nice值的进程的PID。
输入进程PID并回车后,会要求你输入调整后的nice值。
输入nice值后按“q”即可退出。
通过renice命令更改进程的nice值
指令 : renicie nice值 PID
注意: 若是想使用renice命令将NI值调为负值,也需要使用sudo命令提升权限。
其他概念
1.竞争性: 系统进程数目众多,而CPU资源只有少量,甚至1个,所以进程之间是具有竞争属性的。为了高效完成任务,更合理竞争相关资源,便有了优先级。
2.独立性: 多进程运行,需要独享各种资源,多进程运行期间互不干扰。
3.并行: 多个进程在多个CPU下分别同时进行运行,这称之为并行。
4.并发: 多个进程在一个CPU下采用进程切换的方式,在一段时间之内,让多个进程都得以推进,称之为并发
竞争性: 系统进程数目众多,而CPU资源只有少量,甚至1个,所以进程之间是具有竞争属性的。为了高效完成任务,更合理竞争相关资源,便有了优先级。
环境变量
基本概念
环境变量(environment variables)一般是指在操作系统中用来指定操作系统运行环境的一些参数。
环境变量通常具有某些特殊用途,并且在系统当中通常具有全局特性。
常见环境变量
- PATH: 指定命令的搜索路径。
- HOME: 指定用户的主工作目录(即用户登录到Linux系统中的默认所处目录)。
- SHELL: 当前Shell,它的值通常是/bin/bash。
查看环境变量的方法
echo命令:echo $PATH
测试PATH
提出这样一个问题:为什么执行ls命令的时候不用带./就可以执行,而我们自己生成的可执行程序必须要在前面带上./才可以执行?
要执行一个可执行程序必须要先找到它在哪里,既然不带./就可以执行ls命令,说明系统能够通过ls名称找到ls的位置,而系统是无法找到我们自己的可执行程序的,所以我们必须带上./,以此告诉系统该可执行程序位于当前目录下。
而系统就是通过环境变量PATH来找到ls命令的,查看环境变量PATH我们可以看到如下内容:
当你使用ls命令时,系统就会查看环境变量PATH,然后默认从左到右依次在各个路径当中进行查找。
测试HOME
任何一个用户在运行系统登录时都有自己的主工作目录(家目录),环境变量HOME当中即保存的该用户的主工作目录。
测试SHELL
我们在Linux操作系统当中所敲的各种命令,实际上需要由命令行解释器进行解释,而在Linux当中有许多种命令行解释器(例如bash、sh),我们可以通过查看环境变量SHELL来知道自己当前所用的命令行解释器的种类。
和环境变量相关的命令
1、echo:显示某个环境变量的值。
2、export:设置一个新的环境变量。
3、env:显示所有的环境变量。
4、set:显示本地定义的shell变量和环境变量。
5、unset:清除环境变量。
环境变量的组织方式
每个程序都会收到一张环境变量表,环境表是一个字符指针数组,每个指针指向一个以’\0’结尾的环境字符串,最后一个字符指针为空。
通过代码获取环境变量
main函数其实有三个参数,只是我们平时基本不用它们,所以一般情况下都没有写出来。
先谈谈前两个参数:
main函数的前两个参数:
1.main函数的第一个参数代表的就是字符指针数组当中的有效元素个数。
2.main函数的第二个参数是一个字符指针数组,数组当中的第一个字符指针存储的是可执行程序的位置,其余字符指针存储的是所给的若干选项,最后一个字符指针为空。
对于main函数的第三个参数:实际就是环境变量表
也可以通过第三方environ获取:
注意:libc中定义的全局变量environ指向环境变量表,environ没有包含在任何头文件中,所以在使用时 要用extern声明。
程序地址空间
通过下面代码来理解:
我们发现,输出出来的变量值和地址是一模一样的,很好理解呀,因为子进程按照父进程为模版,父子并没有对变
量进行进行任何修改。
可是将代码稍加改动:
我们发现,**父子进程,输出地址是一致的,但是变量内容不一样!**能得出如下结论:
- 变量内容不一样,所以父子进程输出的变量绝对不是同一个变量,但地址值是一样的,说明,该地址绝对不是物理地址!
- 在Linux地址下,这种地址叫做 虚拟地址
- 我们在用C/C++语言所看到的地址,全部都是虚拟地址!物理地址,用户一概看不到,由OS统一管理
注意:OS必须负责将 虚拟地址 转化成 物理地址 。
进程地址空间
所以之前说‘程序的地址空间’是不准确的,准确的应该说成 进程地址空间 ,那该如何理解呢?看图:
关于理解的问题:
1.为什么进程要进行写实拷贝?
进程具有独立性。多进程运行,需要独享各种资源,多进程运行期间互不干扰,不能让子进程的修改影响到父进程。
2、为什么不在创建子进程的时候就进行数据的拷贝?
子进程不一定会使用父进程的所有数据,并且在子进程不对数据进行写入的情况下,没有必要对数据进行拷贝,我们应该按需分配,在需要修改数据的时候再分配(延时分配),这样可以高效的使用内存空间。
3.为什么要有进程地址空间?
a、有了进程地址空间后,就不会有任何系统级别的越界问题存在了。例如进程1不会错误的访问到进程2的物理地址空间,因为你对某一地址空间进行操作之前需要先通过页表映射到物理内存,而页表只会映射属于你的物理内存。总的来说,虚拟地址和页表的配合使用,本质功能就是包含内存。
b、有了进程地址空间后,每个进程都认为看得到都是相同的空间范围,包括进程地址空间的构成和内部区域的划分顺序等都是相同的,这样一来我们在编写程序的时候就只需关注虚拟地址,而无需关注数据在物理内存当中实际的存储位置。
c、有了进程地址空间后,每个进程都认为自己在独占内存,这样能更好的完成进程的独立性以及合理使用内存空间(当实际需要使用内存空间的时候再在内存进行开辟),并能将进程调度与内存管理进行解耦或分离。
Linux2.6内核进程调度队列
注意:一个CPU拥有一个runqueue,如果有多个CPU就要考虑进程个数的父子均衡问题。
优先级
queue下标说明:
- 普通优先级:100~139。
- 实时优先级:0~99。
我们进程的都是普通的优先级,前面说到nice值的取值范围是-20 ~ 19,共40个级别,依次对应queue当中普通优先级的下标100~139。
过期队列
- 过期队列和活动队列的结构相同。
- 过期队列上放置的进程都是时间片耗尽的进程。
- 当活动队列上的进程被处理完毕之后,对过期队列的进程进行时间片重新计算。
active指针和expired指针
- active指针永远指向活动队列。
- expired指针永远指向过期队列。