- 1.linux操作系统要不要管理进程呢?
必须要!!!!!!!!!
- 2.linux是如何管理大量进程的呢?
先组织,再描述。
1.什么是进程
- 进程就是系统运行中的程序(process)进程 = 可执行程序 + 该进程对应的内核数据结构
- 是正在执行的一个程序或者命令,每一个进程都是一个运行的实体,都有自己的地址空间,并占用一定的系统资源
进程会占用四类资源,CPU,memory,disk,network。
- CPU (Central Processing Unit 中央处理单元 )lscpu命令可以看到CPU的详细信息
- Memory(内存)free -h命令可以查看系统内存大小
- Disk(磁盘)
- Network(网络)
2.进程控制块PCB(process control block)
- 我们在开头说到了进程的管理时先组织再描述。
我们可以同时创建好多个进程,那这么多的进程放到内存区后系统要怎么管理呢?谁先谁后或者谁大谁小这些如何区分呢?所以我们需要一个东西来描述每个进程的特征属性来区别不同的进程---PCB
- 我们知道:文件 = 内容 + 属性
把mytest.exe加载到内存里,本质上
只是把内容加载到内存
里,可是我们要管理进程,这里就需要大量的PCB结构来描述
这里的进程,其中PCB包含了进程所有的属性
:(包括了代码在哪、数据在哪、谁启动、什么时间启动的)。
- Linux操作系统下的PCB是:task_struct,task_struct包含了进程中的所有属性信息。PCB->task_struct的关系类似shell->bash的关系。task_struct是Linux内核的一种数据结构,会被装载到内存里。磁盘中的文件或者exe,加载到内存中,操作系统还要创建PCB(进程控制块)。
我们在捋一下,把文件从磁盘加载到内存可以认为是内容的拷贝,但是为了区分这些内容,用每个内容对应的属性来区别,而这些属性都被存放在了PCB中。
有了PCB,所有的进程管理任务和进程对应的程序毫无关系,和进程相关的内核创建的该进程的PCB强相关
task_struct主要包含以下内容:
- 标示符:描述本进程的唯一标示符,用来区别其他进程。
- 状态:任务状态,退出代码,退出信号等。
- 优先级:相对于其他进程的优先级。
- 程序计数器:程序中即将被执行的下一条指令的地址。
- 内存指针:包括程序代码和进程相关数据的指针,还有和其他进程共享的内存块的指针
- 上下文数据:进程执行时处理器的寄存器中的数据[休学例子,要加图CPU,寄存器]。
- I/ O状态信息:包括显示的I/O请求,分配给进程的I/ O设备和被进程使用的文件列表。
- 记账信息:可能包括处理器时间总和,使用的时钟数总和,时间限制,记账号等。其他信息
3.查看进程
- 我们先创建一个Makefile文件:
- 再创建一个myproc.c文件,再进行make操作。
- 运行myproc
- 就会发现死循环了。
- 这里我们再创建一个分屏来实现,一定要创建一个分屏,要不以下功能不一定能实现。
[CTRL+C] 结束循环
我们如何显示现在的进程呢?主要有以下办法:
- 通过ps命令查看进程
- 通过proc查看
通过ps命令查看进程
ps axj | grep 'myproc'
这里用myproc是因为我的文件的名字就叫myproc.
通过proc查看进程目录
ls /
- 注意上面的proc目录,它是一个内存文件系统,里面放的是当前系统实时的进程信息。我们进入此目录看看:
每一个进程在系统中,都会存在一个唯一的标识符,就如同每个学生都有学号一样,而这个唯一标识符在linux称为pid(全称process id),而上面蓝色的数字即为进程的pid。
- 我们可以查看你显示出来的所有的title列名称,输入下面的指令:
ps ajx | head -1
- 这一行显示的就是所有的属性名,我们也可以让他全部显示:
ps ajx | head -1 && ps axj | grep 'myproc'
这个pid十分重要。
- 当我们用ctrl+C结束死循环后
再执行总命令时:
此时我们在重新启动这个程序,用我们的top命令试一下:
top命令也可以帮我们显示所有进程。一般top命令用得少,不细讲了。top命令输入Q就退出了 。
我的pid不再是是上面那个,而是32790
我们可以在目录proc下面查询一下32709此时所有的属性数据就都出现了。
ls /proc/32709
如果还不懂,在命令后面加上-al
- exe目录:表示进程对应的可执行程序的磁盘文件。
- cwd那个目录:是当前进程的工作路径
4通过系统调用获取进程标示符
这里我们在myproc.c文件中在写一个代码:
然后clean;我们再在右边的程序中make一下,然后运行./myproc
根据上面的运行结构可知:运行两次,子进程pid改变。
但是父进程ppid不会改变。
- 我们可以输入下面的指令来查看这个父进程到底是什么:
ps ajx | head -1 && ps axj | grep 20505
图中可以看出父进程13009就是一个bash。几乎我们在命令行上所执行的所有的指令,都是bash进程的子进程!
5.通过系统调用创建进程-fork初识
1.用fork创建一个子进程:
man fork
退出fork文档用【Q】。
fork函数是用来创建子进程的,它有两个返回值:
- 返回成功的话,把子进程的pid返回给父进程,给子进程返回0,
- 失败的话,-1返回给父进程。不创建子进程
为啥返回成功会哟与两个返回值呢?代码不都是一个吗?
我们再来看一个
fork()函数后面的内容父子进程共享。
父子进程对于代码是共享的但是并不意味着两者所执行的代码一模一样,因为两者的pid不一样,尽管两者都能看见fork()后的代码但是并不执行同一份代码。
总结:
fork之后,父进程和子进程会共享代码,一般都会执行后续的代码,这就是为什么printf会打印两次的原因。
fork之后,父进程和子进程返回值不同,可以通过不同的返回值,判断,让父子执行不同的代码块。
- 补充1:fork()为什么给父进程返回子进程的pid,给子进程返回0?
父进程必须有标识子进程的方案,这个方案就是fork之后给父进程返回子进程的pid!
子进程最重要的是要知道自己被创建成功了,因为子进程找父进程成本非常低,只需要getppid()即可,所以只需要给子进程返回0即可。
- 补充2:为什么fork会返回两次?
fork是用来创建子进程,则会导致系统多了一个进程(task_struct+子进程的代码和数据),子进程的task_struct对象内部的数据大部分从父进程继承下来的。子进程和父进程执行同样的代码,fork之后,父子进程代码共享,而数据要各自独立。虽然代码共享,但是可以让不同的返回值执行不同的代码。
调用一个函数,当这个函数准备return的时候,这个函数的核心功能已经完成,子进程已经被创建了,并将子进程放入运行队列,现在父进程和子进程既然都存在,并且代码共享,那么当然return要返回两次。自然就有两个返回值。
4.进程状态
进程状态本质上就是一个整数,在进程的task_struct。
- 进程状态反映进程执行过程的变化。这些状态随着进程的执行和外界条件的变化而转换。在三态模型中,进程状态分为三个基本状态,即运行态,就绪态,阻塞态。在五态模型中,进程分为新建态、终止态,运行态,就绪态,阻塞态。
- 运行态:进程占有处理器正在运行。
- 就绪态:进程具备运行条件,等待系统分配处理器以便运行。
- 等待态:又称为阻塞(blocked)态或睡眠(sleep)态,指进程不具备运行条件,正在等待某个事件的完成。
- 1、进程运行:
运行态指的是进程只要在运行队列中就叫做运行状态,它表明进程要么是在运行中要么在运行队列里。代表我已经准备好了,随时可以调度。
- 2、进程终止:
- 这个进程还在,只不过永远不运行了,随时等待被释放!
- 进程都终止了,为什么部立马释放对应的资源,而要维护一个终止状态?
因为即使你进程终止了,但是你操作系统并不能立马就来释放你,就好比如你在一家餐厅的某个位置吃饭,当你吃饭走人后,这个位置能再次被别人使用吗,当然不能,因为服务员还没来得及收拾你的残渣。所以既然你已经终止了,那么就需要一直维持着终止状态,以此告诉操作系统等你不忙了赶紧来把我释放掉,这就是随时等待被释放。
等待非CPU资源就绪的状态叫做进程阻塞。
- 3、进程阻塞:
- 4、进程挂起(也属于S 状态)
当内存不足的时候,OS提供适当的
置换
进程的代码和数据到磁盘中,PCB不换(好比你学籍还在,人把你赶走了)进程的状态就叫做挂起。 你现在正在等待某种资源的时候,正巧内存不足了,内存不够是你正在阻塞状态,所以把你的代码数据置换到磁盘里,所以叫做“挂起阻塞”引起挂起状态的原因有:
- 对换的需要。为了缓和内存紧张的情况,而将内存中处于阻塞状态的进程换至外存上,使进程又处于一种有别于阻塞状态的新状态。因为即使该进程所期待的事件发生,该进程仍不具备执行条件而不能进入就绪队列,称这种状态为挂起状态。
- 终端用户的请求。当终端用户在自己的程序运行期间,发现有可疑问题时,往往希望使自己的进程暂停下来。也就是说,使正在执行的进程暂停执行,若是就绪进程,则不接受调度以便研究其执行情况或对程序进行修改。把这种静止状态也称为挂起状态。
- 父进程请求。父进程常希望挂起自己的子进程,以便考查和修改子进程,或者协调各子进程间的活动。
5.linux中内核进程状态有:
- 1、运行状态---R
一个进程处于运行状态(running),并不意味着进程一定在运行中,它表明进程要么是在运行中要么在运行队列里。
ps axj | head -1 && ps ajx | grep myproc | grep -v grep
状态是S+,而不是R但是并不代表不是运行状态。
当我们编译运行此程序后,进入死循环,此程序没有访问其它外设资源(读文件、读磁盘……)此进程一直在cpu的队列里,下面来查看此进程的状态:
这里尾删还有+?
状态后面带+说明这个任务是前台进程。
什么是前台进程?前台进程一启动,执行命令就没有效果了并且能被ctrl+c终止。
在后面+&就把前台任务改成后台任务了。并且显示了PID。
- 前台进程:
S+
和后台进程:S 的区别前台进程:
./myproc
,输入指令无效bash的命令行解释器就停止工作了,可以被【Ctrl +C】终止后台进程:
./myproc &
,可以执行指令,【Ctrl +C】 不能终止进程,退出进程要用kill
- 2、浅度睡眠状态(sleeping) ---S,也叫做可中断睡眠(interruptible sleep)
等待非CPU资源就绪。这种休眠是可被换醒的,我们可以
Ctrl + C
退出循环,而此时的进程就没了,也就是说它虽然是一种休眠状态,但是它随时可以接收外部的信号,处理外部的请求。上面的那个S+中的S就是睡眠状态的意思。
- 浅度睡眠:当进程处于S状态,它可以随时被操作系统唤醒。
- 可中断睡眠:当进程处于S状态,它可以被你随时kill杀掉:
- 3、 深度睡眠状态(Disk sleep)---D,也叫不可中断睡眠状态(uninterruptible sleep)
进程处于D状态,不可被杀掉,只能等这个进程自动醒来,kill -9 都杀不掉它,也得等它醒来 (关机除外,有可能关机都要被磁盘写入卡住,只能拔电源)。
- 4、死亡状态(dead)--X
随时准备被OS回收。此状态只是一个返回状态,无法在任务列表中看到这个状态。因为回收进程是一瞬间发生的事情,我们很难直接捕捉到。
- 5、僵尸状态(Zombie)---Z
一个进程已经退出,但还不允许被OS释放,处于一个被检测的状态。
- 6、暂停状态 ---T/t
可以通过发送 SIGSTOP(kill -19) 信号给进程来停止(T)进程。这个被暂停的进程可以通过发送 SIGCONT 信号让进程继续运行。
kill -l
kill -19 PID(自己的)
kill -18 PID
僵尸进程
僵尸状态是一个进程已经退出但是还是不允许被OS释放,处于一个被检测的状态。僵尸进程是处于僵尸状态的进程。
- 僵死状态(Zombies)是一个比较特殊的状态。当进程退出并且父进程(没有读取到子进程退出的返回代码时就会产生僵死(尸)进程
- 僵死进程会以终止状态保持在进程表中,并且会一直在等待父进程读取退出状态代码。
- 所以,只要子进程退出,父进程还在运行,但父进程没有读取子进程状态,子进程进入Z状态
检查端口常用命令:
while :; do ps axj | head -1 && ps axj | grep myproc | grep -v grep; sleep 1; echo "########################"; done
#include<stdio.h> 3 #include<stdlib.h> 4 #include<unistd.h> 5 int main() 6 { 7 pid_t id= fork(); 8 if(id==0) 9 { 10 //child 11 int cnt=5; 12 while(cnt) 13 { 14 printf("我是子进程,我还剩下:%d S\n", cnt--); 15 sleep(1); 16 } 17 printf("我是子进程,我已经是僵尸,等待被检测\n"); 18 exit(0); 19 } 20 else 21 { 22 //father 23 while(1) 24 { 25 sleep(1); 26 } 27 } 28 return 0; 29 }
这时就出现了僵尸进程。
僵尸进程的危害:
- 进程的退出状态必须被维持下去,因为他要告诉关心它的进程(父进程),你交给我的任务,我办的怎么样了。可父进程如果一直不读取,那子进程就一直处于Z状态?是的!
- 维护退出状态本身就是要用数据维护,也属于进程基本信息,所以保存在task_struct(PCB)中,换句话说, Z状态一直不退出, PCB一直都要维护?是的!
- 那一个父进程创建了很多子进程,就是不回收,是不是就会造成内存资源的浪费?是的!因为数据结构对象本身就要占用内存,想想C中定义一个结构体变量(对象),是要在内存的某个位置进行开辟空间!
- 最终会导致内存泄漏。
孤儿进程 :
1
#include<stdlib.h> 4 #include<unistd.h> 5 int main() 6 { 7 pid_t id= fork(); 8 if(id==0) 9 { 10 //child 11 while(1) 12 { 13 printf("hello world!:\n"); 14 sleep(1); 15 } 16 17 } 18 else 19 { 20 //father 21 int cnt=5; 22 while(5) 23 { 24 printf("I am father: %d\n", cnt--); 25 sleep(1); 26 } 27 } 28 return 0; 29 }
- 当父进程退出的时候,父进程为什么没有变成僵尸状态Z呢?
- 因为父进程的父进程是bash,bash会自动回收它的子进程,也就是这里的父进程,换言之这里没有看到僵尸是因为父进程被它的父进程bash回收了。
- 先前我们说过,子进程退出的时候,父进程要通过某种方式回收子进程,但是这里很明显父进程先走了,子进程还没退出,可如果我子进程退出了,那谁来回收我呢?
- 总结:这里操作系统就扮演了干爹的角色,也就是如果父进程提前退出,子进程就会被1号进程(就是操作系统)领养,我们把这种被领养的进程称为孤儿进程。
6.进程优先级
概念:
- cpu资源分配的优先顺序,就是指进程的优先权(priority)。
- 优先权高的进程有优先执行的权利。配置进程优先权对多任务环境的Linux很有用。
- 还可以将进程安排到指定的CPU上,将不重要进程安排到某个CPU,可改善系统整体性能。
- 由于系统中进程数量众多,而CPU资源比较少甚至只有一个,进程之间需要竞争来使用CPU。这时让一个比较重要、需要优先执行的进程去和其他进程竞争,显然是不合理的。为了更合理的分配CPU资源, 就有了进程优先级。
- 优先级高的进程有优先执行的权利。此外,优先级还影响分配给进程的时间片长短。 重要的进程,应该分配多一些cpu时间片,好让其尽快完成任务。所有的进程都会有机会运行,但优先级高的进程会获取更多的cpu执行时间。配置进程优先级对多任务环境的Linux很有用,可以改善系统性能。
查看优先级:
ps -l
我们很容易注意到其中的几个重要信息,如下:
- UID : 代表执行者的身份
- PID : 代表这个进程的代号
- PPID :代表这个进程是由哪个进程发展衍生而来的,亦即父进程的代号
- PRI :代表这个进程可被执行的优先级,其值越小越早被执行
- NI :nice值, nice 是用来修改 PRI 的 , 也就是说用来调整进程优先级的, PRI(new) = PRI(old) + nice。当nice值为负值的时候,那么该程序优先级会变高 (PRI数值降低)。
- 需要强调一点的是,进程的nice值不是进程的优先级,他们不是一个概念,但是进程nice值会影响到进程的优先级变化。nice值是进程优先级的修正修正数据。
查看优先级的的信息:
ps -al
调整进程优先级
用top命令更改已存在进程的nice值来调整进程优先级
- 执行 top 命令。
- 进入top后输入r ===》输入需要修改的进程的进程号PID ===》再输入nice 的值 , 按回车即可。
4个重要概念:
- 竞争性:系统进程数目众多,而CPU资源只有少量,甚至1个,所以进程之间是具有竞争属性的。为了高效完成任务,更合理竞争相关资源,便具有了优先级
- 独立性:多进程运行,需要独享各种资源,多进程运行期间互不干扰
- 并行:多个进程在多个CPU下分别,同时进行运行,这称之为并行
- 并发:多个进程在一个CPU下采用进程切换的方式,在一段时间之内,让多个进程都得以推进,称之为并发