马上注册,结交更多好友,享用更多功能,让你轻松玩转社区。
您需要 登录 才可以下载或查看,没有账号?立即注册
x
讲义概念:步伐的⼀个执行实例,正在执行的步伐等
内核观点:继承分配体系资源(CPU时间,内存)的实体。
进程信息被放在一个叫做进程控制块的数据结构中,可以理解为进程属性的集合.讲义上称之为PCB(process control block),Linux操纵体系下的PCB是: task_struct
task_struct-PCB的⼀种
• 在Linux中形貌进程的结构体叫做task_struct。
• task_struct是Linux内核的一种数据结构,它会被装载到RAM(内存)里并且包含着进程的信息。
可以在内核源代码里找到它。全部运行在体系里的进程都以task_struct链表的形式存在内核里。
内容分类
- 标示符: 形貌本进程的唯一标示符,用来区别其他进程。
- 状态: 任务状态,退出代码,退出信号等。
- 优先级: 相对于其他进程的优先级。
- 步伐计数器: 步伐中即将被执行的下⼀条指令的地址。
- 内存指针: 包罗步伐代码和进程相关数据的指针,还有和其他进程共享的内存块的指针
- 上下文数据: 进程执行时处置惩罚器的寄存器中的数据[休学例子,要加图CPU,寄存器]。
- I∕O状态信息: 包罗体现的I/O请求,分配给进程的I∕O装备和被进程使用的文件列表。
- 记账信息: 可能包罗处置惩罚器时间总和,使用的时钟数总和,时间限定,记账号等。
- 其他信息
检察进程
1. 进程的信息可以通过 /proc 体系文件夹检察
2.top或者ps
- a:体现⼀个终端全部的进程,包罗其他用户的进程。
- x:体现没有控制终端的进程,例如背景运行的守护进程。
- j:体现进程归属的进程组ID、会话ID、父进程ID,以及与作业控制相关的信息
- u:以用户为中央的格式体现进程信息,提供进程的详细信息,如用户、CPU和内存使用环境等
3.通过体系调用获取进程标示符
- 1 #include <stdio.h>
- 2 #include <sys/types.h>
- 3 #include <unistd.h>
- 4 int main()
- 5 {
- 6 while(1){
- 7
- 8 sleep(1);
- 9 printf("我是一个进程!我的pid:%d,我的ppid:%d \n",getpid(),getppid());
- 10 }
- 11 return 0;
- 12 }
- ~
复制代码
创建进程
通过体系调用创建进程-fork初识
- fork有两个返回值
- 父子进程代码共享,数据各自开辟空间,私有⼀份(采用写时拷贝)
- 常用if父子分流
操纵成功,0返回给子进程把pid返回给父进程,返回给父进程-1是创建失败
- #include <stdio.h>
- #include <sys/types.h>
- #include <unistd.h>
- int main()
- {
- int ret = fork();
- if(ret < 0){
- perror("fork");
- return 1;
- }
- else if(ret == 0){ //child
- printf("I am child : %d!, ret: %d\n", getpid(), ret);
- }else{ //father
- printf("I am father : %d!, ret: %d\n", getpid(), ret);
- }
- sleep(1);
- return 0;
- }
复制代码
以是我们可以用if来分流,让父子执行不同的操纵
- 1 #include <stdio.h>
- 2 #include <sys/types.h>
- 3 #include <unistd.h>
- 4 int main()
- 5 {
- 6 printf("程序开始运行pid:%d,",getpid());
- 7
- 8 pid_t id = fork();
- 9 if(id < 0){
- 10 perror("fork");
- 11 return 1;
- 12 }
- 13 else if(id == 0)
- 14 {
- 15 //child
- 16 while(1)
- 17 {
- 18 sleep(1);
- 19 printf("我是一个子进程,我的pid:%d ,我的父进程pid:%d\n",getpid(),getppid());
- 20 }
- 21 }
- 22 else{
- 23 //father
- 24 while(1)
- 25 {
- 26 sleep(1);
- 27 printf("我是一个父进程,我的pid:%d ,我的父进程pid:%d\n",getpid(),getppid());
- 28
- 29 }
- 30 }
- 31 return 0;
- 32 }
复制代码
fork为什么会有两个返回值?
fork也是函数,它是一个体系调用,fork创建子进程,父子各一次return
两个返回值各种给父子怎样返回?
fork也是函数,它是一个体系调用,fork创建子进程后,子进程进行写实拷贝父进程,父进程里的id写给子进程
写实拷贝
创建子进程中,子进程拷贝父进程的代码和页表等的数据后各自独立一份单独执行,从这里也可以间接印证,数据并不是存在现实的地址中,一个变量怎么可能会有两个值呢?只不外是一种映射方式,涉及到虚拟地址后面再谈
- 1 #include <stdio.h>
- 2 #include <sys/types.h>
- 3 #include <unistd.h>
- 4 int a=100;
- 5 int main()
- 6 {
- 7 printf("程序开始运行pid:%d,",getpid());
- 8
- 9 pid_t id = fork();
- 10 if(id < 0){
- 11 perror("fork");
- 12 return 1;
- 13 }
- 14 else if(id == 0)
- 15 {
- 16 //child
- 17 while(1)
- 18 {
- 19 a++;
- 20 sleep(1);
- 21 printf("我是一个子进程,我的pid:%d ,我的父进程pid:%d,a:%d\n",getpid(),getppid(),a);
- 22 }
- 23 }
- 24 else{
- 25 //father
- 26 while(1)
- 27 {
- 28 sleep(1);
- 29 printf("我是一个父进程,我的pid:%d ,我的父进程pid:%da:%d\n",getpid(),getppid(),a);
- 30
- 31 }
- 32 }
- 33 return 0;
- 34 }
- ~
复制代码
进程状态
运行&&壅闭&&挂起
运行:进程在调度队列中,进程的状态都是R
壅闭:等候某种装备或资源停当
挂起:把运行队列中的进程交换到磁盘的swap交换分区里面
创建子进程是为了使子进程执行某种命令,子进程回返回父进程一种状态(可以用ps命令来查)
- /*
- *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的竣事。
- T制止状态(stopped): 可以通过发送 SIGSTOP 信号给进程来制止(T)进程。这个被停息的进程可以通过发送 SIGCONT 信号让进程继续运行。
- X死亡状态(dead):这个状态只是⼀个返回状态,你不会在任务列表里看到这个状态。
僵尸进程
创建子进程是为了使子进程执行某种命令,僵尸进程是进程状态的一种,子进程一种维持在一种状态而父进程迟迟获取不到子进程具体的退出信息,容易内存泄漏!
- 1 #include <stdio.h>
- 2 #include <stdlib.h>
- 3 #include<unistd.h>
- 4 #include<sys/types.h>
- 5 int main()
- 6 {
- 7 pid_t id = fork();
- 8 if(id < 0){
- 9 perror("fork");
- 10 return 1;
- 11 }
- 12 else if(id > 0)
- 13 { //parent
- 14 printf("parent[%d] is sleeping...\n", getpid());
- 15 sleep(30);
- 16 }else{
- 17 printf("child[%d] is begin Z...\n", getpid());
- 18 sleep(5);
- 19 exit(EXIT_SUCCESS);
- 20 }
- 21 return 0;
- 22 }
复制代码
僵尸进程的危害
- 进程的退出状态必须被维持下去,由于他要告诉关心它的进程(父进程),你交给我的任务,我办的怎么样了。可父进程如果⼀直不读取,那子进程就⼀直处于Z状态?是的!
- 维护退出状态自己就是要用数据维护,也属于进程基本信息,以是生存在task_struct(PCB)中,换句话说,Z状态⼀直不退出,PCB⼀直都要维护?是的!
- 那一个父进程创建了许多子进程,就是不接纳,是不是就会造成内存资源的浪费?是的!由于数据结构对象自己就要占用内存,想想C中定义⼀个结构体变量(对象),是要在内存的某个位置进行开辟空间!
那什么样的进程具有内存泄漏,是比力麻烦的?
进程退出了,内存泄漏就不存在了。但常驻内存的进程是给PCB维护的,释放不归操纵体系管,内存释放就交给用户了,容易内存泄漏。
孤儿进程
父进程创建子进程,父进程在子进程未竣事之前竣事,子进程此时就是孤儿进程,被1号进程领养
- #include <stdio.h>
- 2 #include <unistd.h>
- 3 #include <stdlib.h>
- 4 int main()
- 5 {
- 6 pid_t id = fork();
- 7 if(id < 0){
- 8 perror("fork");
- 9 return 1;
- 10 }
- 11 else if(id == 0){//child
- 12 while(1){
- 13 printf("I am child, pid : %dppid:%d\n", getpid(),getppid());
- 14 sleep(1);
- 15
- 16 }}
- 17 else{//parent
- 18 int cnt=5;
- 19 while(cnt--){
- 20 printf("I am parent, pid: %dppid:%d\n", getpid(),getppid());
- 21 sleep(1);
- 22 exit(0);
- 23 }
- 24 }
- 25 return 0;
- 26 }
- ~
复制代码 孤儿进程使用ctrl c杀不死,由于ctrl c杀不死他是背景进程 (./cmd &也能酿成背景进程)得使用 kill -9
进程优先级
- cpu资源分配的先后序次,就是指进程的优先权(priority)。
- 优先权高的进程有优先执行权利。设置进程优先权对多任务环境的linux很有用,可以改善体系性能。
- 还可以把进程运行到指定的CPU上,这样⼀来,把不重要的进程安排到某个CPU,可以大大改善体系整体性能。
UID : 代表执行者的身份
• PID : 代表这个进程的代号
• PPID :代表这个进程是由哪个进程发展衍生而来的,亦即父进程的代号
• PRI :代表这个进程可被执行的优先级,其值越小越早被执行默以为80
• NI :代表这个进程的nice值
PRI即进程的优先级,或者通俗点说就是步伐被CPU执行的先后序次,值越小进程的优先级别越高
那NI就是nice值,其体现进程可被执行的优先级的修正数值
PRI值越小越快被执行,那么加入nice值后,将会使得PRI变为:PRI(new)=PRI(old)+nice
这样,当nice值为负值的时候,那么该步伐将会优先级值将变小,即其优先级会变高,则其越快
被执行。以是,调解进程优先级,在Linux下,就是调解进程nice值(nice其取值范围是-20至19,⼀共40个级别。)
平凡优先级:100〜139(我们都是平凡的优先级,想想nice值的取值范围,可与之对应!)
及时优先级:0〜99(不关心)
需要强调一点的是,进程的nice值不是进程的优先级,他们不是一个概念,但是进程nice值会影
响到进程的优先级变化。可以理解nice值是进程优先级的修正修正数据
调解优先级
调解获得cpu资源的先后序次,NI值越低,优先级越高,反之优先级越低、基于时间片的分时操纵体系,考虑公平性,优先级可能会发生变化,但是变化幅度不会相差太大,默认80,每次修改都是从80开始操纵nice范围是-20~19,linux进程优先级为60~99
用top命令更改已存在进程的nice:
- top
- 进入top后按“r”‒>输入进程PID‒>输入nice值
留意:
- 其他调解优先级的命令:nice,renice
- 体系函数:
让它加10,改完酿成90,怎么还调低了呢,参考下文优先级队列
优先级设立不合理,会导致优先级低的进程长时间得不到cpu资源,进而导致:进程饥饿
增补概念-竞争、独立、并行、并发
- 竞争性: 体系进程数目众多,而CPU资源只有少量,甚至1个,以是进程之间是具有竞争属性的。为了高效完成任务,更合理竞争相关资源,便具有了优先级
- 独立性: 多进程运行,需要独享各种资源,多进程运行期间互不干扰
- 并行: 多个进程在多个CPU下分别,同时进行运行,这称之为并行
- 并发: 多个进程在一个CPU下采用进程切换的方式,在一段时间之内,让多个进程都得以推进,称之为并发
进程切换
CPU上下文切换:其现实含义是任务切换, 或者CPU寄存器切换。当多任务内核决定运行另外的任务时, 它生存正在运行任务的当前状态, 也就是CPU寄存器中的全部内容。这些内容被生存在任务自己的堆栈中, 入栈工作完成后就把下一个将要运行的任务的当前状态从该任务的栈中重新装入CPU寄存器,并开始下一个任务的运行, 这一过程就是context switch。
当进程要把自己的进程硬件上下文的数据报存下来,生存到那里???
生存到tast_steuct里!!!
进程切换,最核心的,就是生存和规复当进步程的硬件上下文的数据,及cpu寄存器的内容
linux源码
时间片:当代盘算机都是分时操纵体系,没有进程都有它符合的时间片(其实就是⼀个计数
器)。时间片到达,进程就被操纵体系从CPU中剥离下来。
Linux2.6内核进程O(1)调度队列
从该结构中,选择⼀个最符合的进程,过程是怎么的呢?
1. 从0下表开始遍历queue[140]
2. 找到第⼀个非空队列,该队列肯定为优先级最高的队列
3. 拿到选中队列的第⼀个进程,开始运行,调度完成!
4. 遍历queue[140]时间复杂度是常数!但照旧太低效了!
- bitmap(unsigned int bitmap[5]):用位图二进制0体现没有,1体现该队列节点有进程链,方便快速遍历队列一共140个优先级,一共140个进程队列,为了提高查找非空队列的效率,就可以用5*32个比特位体现队列是否为空,这样,便可以大大提高查找效率!
- array[0],arry[1]:双队列中进行,若只有一个调度队列的话,只有上一个节点跑完,下一个节点才气得到资源,那这样的话,进程优先级低的长时间就得不到cpu资源防止进程饥饿
- 一个CPU拥有⼀个runqueue如果有多个CPU就要考虑进程个数的负载平衡问题
- 时间片还没有竣事的全部进程都按照优先级放在该队列
- nr_active: 总共有多少个运行状态的进程
- queue[140]: 一个元素就是一个进程队列,雷同优先级的进程按照FIFO规则进行排队调度,以是,数组下标就是优先级!
过期队列和活动队列结构一模一样
• 过期队列和活动队列结构模样
• 过期队列上放置的进程,都是时间片耗尽的进程
• 当活动队列上的进程都被处置惩罚完毕之后,对过期队列的进程进行时间片重新盘算
active指针永远指向活动队列,expired指针永远指向过期队列
• 可是活动队列上的进程会越来越少,过期队列上的进程会越来越多,由于进程时间片到期时一直
都存在的。
•在符合的时候,只要可以或许交换active指针和expired指针的内容,就相当于有具有了一批新的活动进程
以是来新进程时链入expired队列里,等active队列跑完,然后和expired进行交换,循环往复。调解优先级也是链在expired里调解优先级,以是上文的+10,反而变慢了,在过期队列里进程优先级提高了,但是相比原来的就变慢了
总结:在体系当中查找一个最符合调度的进程的时间复杂度是⼀个常数,不随着进程增多而导致时间成本增加,我们称之为进程调度O(1)算法!
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。 |