在社保网站做调动,网站如果不备案,重庆app制作开发商,设计软件有哪几种文章目录 #x1f347;1. 什么是进程#x1f348;1.1 概念#x1f348;1.2 理解进程 #x1f34b;2. Linux的PCB#x1f34e;3. 查看进程 杀死进程#x1f352;4. 系统调用获取进程标识符#x1f353;4.1 进程PID#x1f353;4.2 父进程PPID #x1f346;5. 系统… 文章目录 1. 什么是进程1.1 概念1.2 理解进程 2. Linux的PCB3. 查看进程 杀死进程4. 系统调用获取进程标识符4.1 进程PID4.2 父进程PPID 5. 系统调用创建进程fork函数6. 进程状态6.1 操作系统进程状态6.11 运行状态6.12 阻塞状态6.13 挂起状态 6.2 Linux状态如何维护6.21 R状态6.22 S状态6.23 D状态6.24 T t状态6.25 X状态6.26 Z状态 7. 进程优先级7.1 什么是优先级7.2 为什么要有优先级7.3 查看优先级7.4 修改优先级 8. Linux内核O(1)调度算法 1. 什么是进程
1.1 概念
我们的一个程序要运行起来要先加载到内存当中如果这个程序已经加载到了内存当中那么这就叫一个进程。
如图演示一个简单的进程 Windows所打开的一些进程 我们每次在开机的时候需要等个几秒钟这个过程其实就是将操作系统从外设加载内存当中 1.2 理解进程
我们每次使用电脑的时候往往都会打开很多进程要是不用了要么放在后台不管要么就直接关闭了。
所以在操作系统中一般都不是只有一个进程在运行多个进程运行肯定会这有着不同的状态这就需要操作系统将这些进程给管理起来这个理念就和操作系统管理软硬件的理念一样先描述、再组织。
在形成进程的时候操作系统就会先创建描述进程的属性结构体对象——PCBprocess control block进程控制块而操作系统是用C语言写的C语言描述一个对象使用的是struct。 当我们创建进程时因为操作系统已经将进程描述好了所以当这个进程出现时就会根据该进程的PCB类型为改进程创建对应的PCB对象。当一个进程要运行还需要将该进程的代码和数据加载到内存当中。
那么我们就能知道进程内核的PCB数据结构对象我们所写的代码和数据。 操作系统要管理进程本质上就是管理整个PCB数据结构对象我们可通过这个结构体里面的指针信息从而找到这个进程的代码和数据。而多个进程其实就是将这些结构体对象链接起来这样就转换成了对于单链表的增删改查 2. Linux的PCB
上面讲的即是对于所有操作系统的实现原理但原理归原理具体的落实不同的操作系统还是会有一些差别。
对于Linux具体的PCB叫做task_struct它是一个大型的结构体里面包含了Linux内核中描述进程所以的属性。 task_struct属于PCB的一种 task_struct内容分类
标识符进程的pid状态任务的状态优先级相当于其他进程的优先级内存指针找到代码和数据程序计数器程序中即将被指向的下一条指令的地址记账信息进程所占用的资源… … …
在之后的描述中不再说PCB而是采用task_struct方式用双向链表组织。
3. 查看进程 杀死进程
查看指定进程
ps ajx | head -1 ps ajx | grep myprocess我们发现明明查看的是myprocess的进程但还多了一个。这其实是grep命令的进程因为它在过滤进程信息的时候首先它得把自己变成一个进程然后才能被cpu调度。如果不想看到这个grep进程可采用
ps ajx | head -1 ps ajx | grep myprocess | grep -v grep查看所有进程
ls /proc这个指令查看的是Linux系统中正在执行的所有进程里面也包含了我们刚刚所启动的这个“进程”示例 Tips: 为什么我们在创建文件的时候会默认在当前目录呢 这其实就是因为进程在启动时有自己的工作目录当我们创建这个文件的时候系统会默认将这个路径拼接到该文件前面 我们查看进程就能获取到这个进程的PID我们可通过kill指令来杀死这个进程
kill -9 PID4. 系统调用获取进程标识符
我们要在程序中查看当前进程的标识符可通过getpid接口来获取。
进程idPID父进程idPPID 4.1 进程PID
先将代码稍微改一下 然后我们采用指令对该进程进行监控
while :; do ps ajx | head -1 ; ps ajx | grep myprocess | grep -v grep; echo -----------------; sleep 1; done然后运行这个进程 我们可以看到用ps工具查看的PID和我们自己所输出的PID是一样的
4.2 父进程PPID
我们调用查看父进程标识符的接口发现每次杀死进程之后然后又重新启动这个进程该进程的id会变可是父进程的id却始终不变 用ps指令查看发现这个父进程是bashbash是命令行解释也是就是属于“媒婆”的角色我们所输入的一些指令进程基本上都是bash的子进程
ps ajx | head -1 ps ajx | grep 16805. 系统调用创建进程fork函数
通过man手册查看fork函数我们发现如果创建成功它有2个返回值将子进程的PID返回给父进程然后再返回0给子进程。 我们之前所学的函数或者自己写的函数一般都是只有1个返回值而这里有2个我们可通过代码验证一下。
#includestdio.h
#includeunistd.h
#includesys/types.h
int main()
{printf(begin:PID:%d PPID:%d\n,getpid(),getppid());pid_t id fork();if(id 0){//子进程while(1){printf(子进程,PID:%d PPID:%d\n,getpid(),getppid());sleep(1);}}else if(id 0){//父进程while(1){printf(父进程,PID:%d PPID:%d\n,getpid(),getppid());sleep(1);}}else{//error}return 0;
}
运行发现确实是同时走了2个不同的判断语句而通过父子关系进程查看fork函数创建出了一个子进程 那这里fork函数为什么要给子进程返回0给父进程返回子进程的PID呢 这是为了区分让不同的执行流去执行不同的代码块fork之后的代码是共享的 而这里给父进程返回子进程的PID是为了让父进程区分子进程用来标记子进程子进程只有一个父亲直接调用getppid就能获得父进程的PID。 那这又是如何做到返回2次呢 我们前面提到进程是由内核的数据结构代码和数据所组成的而我们fork创建子进程之后系统会为这个子进程创建task_struck可是这个子进程并没有自己的代码和数据结构所以这个子进程只能访问和父进程一样的代码fork之后父子进程代码共享。 当走到结束的时候子进程返回一次父进程返回一次所以这就有了2个返回值 另外呢由于这两个进程是独立的这也就意味着父子进程的数据不是共享的。子进程的数据修改不影响父进程父进程的数据修改不影响子进程。在这个过程中如果操作系统发现子进程需要用到代码里面的数据就会单独开一块空间给子进程这个空间里面就有子进程所需要的数据这种方式称为数据层面的写时拷贝。 那为什么要创建子进程呢 这是为了让父子进程执行不同的代码块从而协同起来。 如果父子进程创建好了fork()之后谁先运行呢 这个谁先运行其实也说不准因为这个是由系统的调度器决定的
如果我们不使用fork创建子进程我们所写的程序它自己也是一个子进程它的父进程是bash这也就说明了bash肯定是使用到了fork 6. 进程状态
6.1 操作系统进程状态
6.11 运行状态
系统中会有多个进程这些进程由双链表链接起来而系统只需要找到这个链表的头部即可遍历到所以的进程。
而进程是十分多的但CPU只有一个所以这些进程是需要去竞争这个CPU的资源的。CPU会去维护runqueue运行队列
struct runqueu
{//运行队列struct task_struct* head;struct task_struct* tail;//...
}如果CPU要运行某个进程就直接在运行队列当中挑选一个进程加载到内存。 凡是处于运行队列里面的进程我们称之为R态(运行态)但这里可能会有疑惑明明这些进程没有运行为什么会称为R态呢
这是因为这些进程已经准备好了随调随用。 如果一个进程放到了CPU上运行并不是一定到等到它执行完毕才会停止每个进程都有一个时间片比如说这个进程只能运行10ms如果超过了时间那么CPU会将这个进程放到队列的尾部重新排队。 有了时间片这就可以让队列里面的进程在一段时间内都会被运行这种就称为并发执行。 将进程放入CPU然后再拿下来切换新的进程这个动作称为进程切换。 6.12 阻塞状态
操作系统管理硬件设备是通过先描述再组织进行管理那么这些管理这些硬件设备也是有对应的结构体对象。
我们C语言或者C在使用scanf或者cin的时候如果我们键盘不输入数据那么这个进程就会进入键盘的等待队列不会进入系统的运行队列如果有其他进程也需要从键盘读取数据那同样进入等待队列排队。每个设备可能都会被不同的进程所访问如果这个设备就绪了就能读取如果没有就绪就进入这个设备的等待队列。如果这个设备就绪时那么这个进程就会被唤醒进入运行队列。
这种在等待特定设备的进程称之为该进程处于阻塞状态。 6.13 挂起状态
假设有很多个进程在等待键盘这个设备输入可是这个键盘一直不输入然后后面进入等待队列的进程越来越多这就会导致操作系统内部的内存资源被占用过多导致资源不足。那操作系统就在保证正常运行的状况下节省出资源。在排队的进程占用了内存但并没有做什么操作所以操作系统会将这些进程的PCB保留而进程的数据和代码重新放入外设。意思就是只留一个PCB在这排队到时候轮到这个进程了再重新放入内存。这个过程叫做换入和换出也就是挂起状态。这也操作系统就能节省出一大部分空间。
6.2 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 */
}; 6.21 R状态
这两段代码明明都是在运行可是一个是S状态、一个是R状态。 这里我们其实小看了CPU的处理速度 第一段代码只是进行了判断这点判断对应CPU来说并不算什么 而第二段代码需要进行输出也是就是需要访问显示屏这个硬件设备所以我们这个进程可能在不停地访问硬件设备这就有极大的可能进入硬件的等待队列 这里的R或者S后面跟了一个号这其实是说明此时的运行进程是前台运行意思就是这个进程在运行的时候我们输入不了其他的指令这就是前台运行。
R状态就是Linux里面的运行状态。
6.22 S状态
C语言里面的scanf标准输入就是一个很直观的让程序进入S状态的例子
#includestdio.h
int main()
{int a 0;printf(Enter:);scanf(%d,a);printf(a %d\n,a);return 0;
}在Linux中的阻塞状态就是S状态。 许多进程都是处于S状态 6.23 D状态
D状态在Linux里面也是一种阻塞状态我们称为深度睡眠。上面的S状态是浅度睡眠浅度睡眠的进程是可以被用户或者操作系统直接唤醒的运行或者杀死。
而深度睡眠是不响应操作系统的任何请求打个比方 进程要向磁盘写入1GB的数据可是访问磁盘的效率十分低下需要花费一定的时间。 在写的过程中这个进程就一直需要等待磁盘写完然后反馈结果。可是操作系统并不知道在写数据感觉这个进程长时间没有响应就把这个进程杀掉了。这就导致数据还没写完进程就被杀死了这时候写的数据要么存起来、要么直接丢弃大部分都是直接丢弃。那如果这个数据十分重要则需要把这个进程设为深度睡眠状态也就是拿一“免死金牌”不响应系统的请求。 如果系统里面出现了几个的D状态这就说明系统已经快崩溃了。
6.24 T t状态
T状态我们称为暂停状态可采用指令kill -19 PID让这个进程暂停如果要继续运行采用指令kiil -18 PID 我们在测试代码时用的调试就是一个很典型的T状态 6.25 X状态
X状态是一种死亡状态意思就是这个进程运行完毕了也就是我们操作系统概念里面的终止状态。
6.26 Z状态
当一个进程在进入死亡的时候并不会立即进入死亡状态而是进入Z状态我们称为僵尸状态。 打个不恰当的比方 某个人经常性的作息不规律、不好好吃饭在某个晚上突然心梗了一下子没缓过来死亡了。 这时候肯定会先拨打120来现场看看是否能救打110来现场检查是否有他杀嫌疑。 这一系列检查完毕才能正式判定此人意外心梗死亡。 而在检查的过程并不确定我们称为“僵尸”。 一个进程也是如此当一个进程进入死亡状态时操作系统会维护这个进程的状态收集这个进程的信息以让“关系”这个进程的父进程知道它的子进程要死亡了。当父进程知道之后操作系统才会正式回收这个进程让其进入死亡状态。
我们用下面这段代码来进行测试
#includestdio.h
#includeunistd.h
#includesys/types.hint main()
{pid_t id fork();if(id 0){int cnt 5;while(cnt--){printf(子进程:pid:%d ppid:%d cnt %d\n,getpid(),getppid(),cnt);sleep(1);}}else if(id 0){ while(1){printf(父进程:pid:%d ppid:%d\n,getpid(),getppid());sleep(1);}}else{perror(fork error);}return 0;
}这里我们的父进程并没有对子进程进行处理所以子进程这里一直是处于Z状态它的相关信息不能被释放。这就导致了内存泄漏的问题。
那如果父进程先退出它的子进程还在运行会导致什么样的结果呢 观察发现当父进程先退出之后这个子进程的父进程id变成了1 查阅发现这个进程id为1的是操作系统进程这种父进程为1号的进程我们称之为孤儿进程。把这种行为称为该进程被系统所领养。
因为这个进程也会退出需要释放所以要找“监护人”领养。
7. 进程优先级
7.1 什么是优先级
权限决定着能还是不能而优先级是在能的基础上决定着先后顺序。
7.2 为什么要有优先级 打个比方 我们去食堂吃饭如果人比较多是需要对排队的先来排队的人先打饭后到的人在后面排队。 这是一个制度我们都遵守才会有条不紊的运行。 如果没有这个制度将会一团糟都想着先打饭这也到最后谁也吃不成 这也侧面反映人多但是窗口不够所以只能采取排队的方式运行 对于系统也是大部分电脑都只有一个CPU而那么多的进程一个CPU肯定不能一次性全部处理所以进程之间是有竞争关系的。
为了让这些进程“良心竞争”所以操作系统需要确认这些进程的优先级。如果一个进程长时间得不到CPU的资源该进程的代码长时间无法得到推进那么就会造成进程的饥饿问题。
7.3 查看优先级
在Linux中要查看某个进程的优先级使用指令ps -al查看 PRI进程优先级值越小优先级越高 NI优先级修正数值 PRINIPRI(new) PRI(old)NI就表示这个进程新的优先级这个PRI(old)一直都是初始的值 当NI为负数的时候就代表改进程的优先级提高了 使用在Linux中调整优先级就是调整NI的值 NI值的范围[-2019]40个级别
7.4 修改优先级
我们采用top修改优先级首先top查看进程然后r会看到提示 输入要修改的进程pid然后输入修改的值但如果是普通用户没有权限修改所以要提升权限 权限提升之后我们就可以修改完成 再次查看进程的pid修改成功 8. Linux内核O(1)调度算法
每个CPU都要维护一个运行队列
struct runqueue
{task_struct* running[140]; //0~99下标给其他种类进程使用task_struct* waiting[140]; //100~139下标给我们使用也就是40个级别
}这两个是指针数字内存里面存在2张映射表里面存放的都是进程task_struct的地址。
假设我们进程的pid是60然后将这个结构体的地址存进这个映射表如果之后再来同等优先级的进程则直接排到这个进程的后面即可如果来的是进程pid是61那只需要排到下一个地址即可。 这个过程中可能还会有新的进程不断加入然后这里有增加了2给指针来指向运行队列和等待队列
tast_struct** run;
tast_struct** wait;当运行队列里面的进程全部加载完毕之后直接交换run和wait指向的内容swap(run,wait)这也就又有了一批新的运行队列 这里需要判断队列是否为空需要遍历整个数组使用里面还定义了一个成员
bitmap isempty;用每个位置所对应的比特位是否为空如果不为空则找出二进制序列中最近的比特位为1的位置。 这样就能以几乎O(1)的时间复杂度来调度。 那本期的分享就到这里咯我们下期再见如果还有下期的话。