Linux操作系统学习(了解PCB)

了解PCB

struct task_struct

{

- 标示符: 描述本进程的唯一标示符,用来区别其他进程。 
- 状态: 任务状态,退出代码,退出信号等。 
- 优先级: 相对于其他进程的优先级。 
- 程序计数器: 程序中即将被执行的下一条指令的地址。 (也就是永远指向下正在运行的一条指令)
- 内存指针: 包括程序代码和进程相关数据的指针,还有和其他进程共享的内存块的指针 (本质就是通过该指针找到代码和数据的所在位置)
- 上下文数据: 进程执行时处理器的寄存器中的数据。 
- I/O状态信息: 包括显示的I/O请求,分配给进程的I/O设备和被进程使用的文件列表。 
- 记账信息: 可能包括处理器时间总和,使用的时钟数总和,时间限制,记账号等。(用于较为均衡的调度每个进程,例如轨道交通的调度)
-  其他信息:
         竞争性: 系统进程数目众多,而CPU资源只有少量,甚至1个,所以进程之间是具有竞争属性的。为了高 效完成任务,更合理竞				争相关资源,便具有了优先级 
         独立性: 多进程运行,需要独享各种资源,多进程运行期间互不干扰 
         并行: 多个进程在多个CPU下分别,同时进行运行,这称之为并行 
         并发: 多个进程在一个CPU下采用进程切换的方式,在一段时间之内,让多个进程都得以推进,称之为 并发

};

下面介绍几个基础部分的概念


标识符

int pid——代表这个进程的标识符

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-hWqCufwX-1671610915802)(G:\Typora\图片保存\image-20221129193448088.png)]

​ 如上图所示,在linux下编写了这样一段代码,并生成了一个可执行程序myproc,由于程序是死循环,因此我们新打开一个窗 口用来查看进程

​ 结束进程可以在上面的窗口中 ctrl+z ,或者下面的窗口输入 kill -9 + pid

​ 如图所示输入kill -9+对应的pid号,就可以结束该对应的 进程,所以在每个 task_struct 数据结构中,都存在一个如 int pid 这 样的标识符,可以理解为进程号,例如每个学生的学号,每个人的身份证号。

​ 除了pid,还有ppid,代表的是pid的父pid,也就是当前进程的父进程 可以使用getppid()函数查看

状态

进程状态的意义:方便OS快速判断进程

例如:退出码

int code,exit_code————退出码,代表进程的退出信号/状态等

每个主函数结束时都会加一句return 0,0就是退出码

echo ? ? ? 表示 查看最近执行命令的退出码,所以第一次查看是200,第二次就是0,0表示的是echo $? 的退出码,说明每个程序都有一个退出码

所以int code,exit_code代表的就是进程结束的退出码

除了上述的举例还有一些状态如下:

  • R运行状态(running): 并不意味着进程一定在运行中,它表明进程要么是在运行中要么在运行队列里。

​ 如图所示:PCB描述该进程的状态为R就表示是运行状态,多个运行状态的PCB组成数据结构(运行队列),随时可以被CPU调度,CPU对运行队列中的PCB增删改查,等于间接对对应的进程操作。

  • S睡眠状态(sleeping): 意味着进程在等待事件完成(可中断睡眠)

  • D磁盘休眠状态(Disk sleep),在这个状态的 进程通常会等待IO的结束。(不可中断睡眠,不可被kill)

​ 例如,我们电脑上开的软件,如浏览器,假如这时网卡还未启动,那么浏览器的状态就是(S),在等待网卡启动,网卡启动后,该状态由S变为R加载到运行队列中。

​ 或者我们写C语言时,scanf 函数从键盘读取输入,我们不按键盘,scanf就会一直等待

​ 再或者windows中有时会有未响应,让我们选择继续等待或者结束进程

所以进程在不同的队列里,所代表的状态是不一样的,我们把从运行状态(队列)的task_struct放到等待队列中就叫做 挂起等待(阻塞),从等待队列放到运行队列被CPU调度就叫做唤醒进程

执行如下代码所示:

  • T停止状态(stopped): 可以通过发送 SIGSTOP 信号给进程来停止(T)进程。这个被暂停的进程可以通过发送 SIGCONT 信号让进程继续运行。

  • X死亡状态(dead):这个状态只是一个返回状态,你不会在任务列表里看到这个状态,该状态会回收进程的资源,相关的数据结构(PCB),它的代码和一些内核数据都会被回收,什么都不剩。

  • 僵死状态(Zombies):是一个比较特殊的状态。当进程退出并且父进程没有读取到子进程退出的返回代码时就会产生僵死(尸)进程 僵死进程会以终止状态保持在进程表中,并且会一直在等待父进程读取退出状态代码。

    进程结束时不立即释放,而是先进入僵尸状态,僵尸状态是用来存储进程退出的信息,之后才会变为X状态,最后才会释放

​ 僵尸进程危害:

  1. 当子进程进入僵尸状态后会一直保持,因为他要反馈给父进程让父进程读取自己的信息,若父进程一直不读取,他就一直保持僵尸状态;

  2. 僵尸状态一直保持,那他的数据信息等等就会一直存在PCB中,那PCB就要一直维护该进程的信息;

  3. 若一个父进程创建很多子进程,若大多都如上述一样一直保持在僵尸状态,那就代表要一直维护大片数据,占用大片空间,一直不回收就造成资源浪费,也就等于内存泄漏

  • 孤儿进程:当先杀死父进程,那么子进程就变成了 ”孤儿“,会被操作系统”领养“。

优先级

  • UID : 代表执行者的身份

  • PID : 代表这个进程的代号

  • PPID :代表这个进程是由哪个进程发展衍生而来的,亦即父进程的代号

  • PRI :代表这个进程可被执行的优先级,其值越小越早被执行(值越小优先级越高)

  • NI :代表这个进程的nice值 ,nice值可以修PRI,nice其取值范围是-20至19,一共40个级别(PRI = PRI + NI)

    没有绝对优先级,优先级都是相对的,比如不能直接将一个子进程的优先级设置成1,会造成不可预知的错误,所以NI有取值范围,(保证进程的优先级较为均匀,保护系统运行的优先级不可被超过等等)

上下文数据

​ 在这些进程的优先级相等的情况下,是按顺序执行的;谁的PCB到队列出口,就把对应的进程的代码和数据加载到CPU中执行,会执行完一个后再执行下一个;但是每个进程的执行时间是不一样的,而电脑给人眼的感觉是并行运行的,

(如图所示,浏览器和xshell开了多个)

实际则是,CPU限制了每个进程单次的执行时间。

​ 例如每个进程单次的执行时间最多是1ms,若中途执行完则会加载下一个,假如没执行完但到了1ms,他会先把当前没执行完的 进程的PCB重新入队列,随后加载队列顶端的PCB对应的进程数据和代码,就这样每个进程给1ms的单次执行时间循环往复。

​ 实际中CPU的执行速度是非常快的,人的肉眼是看不出来的,就像一个灯以非常快的速度开关开关,虽然它有关的状态,但是给人眼的感觉就是一直亮的;这就是CPU加载并执行进程的基本模型,每个进程的单次执行时间是一样的,若时间到但没执行完就会重新入队列等待再次循环到队列顶端加载(真实的情况要复杂的多)

那么有一个问题,当一个进程数据被二次加载,CPU是重新开始执行还是接着上次结束地方执行呢?是怎么做到的呢?

​ 肯定是接着上次的执行;每个CPU只有一组寄存器,当一个进程的数据加载到CPU执行时寄存器会产生临时数据,在执行时间到但没执行完时,就会读取寄存器中的数据存到对应的PCB中,当下一次执行时就会先从PCB中读取数据到寄存器中再接着执行,

​ 寄存器中的数据就是上下文数据,先从进程读取到寄存器,再从寄存器写入到进程,

​ 例如我们的饭卡每次刷时,读卡机就会读取里面的余额并扣除相应金额。那个读卡机临时读取的数据


查看进程的方式

ps axj | head -1 && ps axj| grep "关键字"

进程的信息可以通过 /proc 系统文件夹查看(程序就是文件)


了解fork()

可以先观察使用fork的现象,再去理解进程

通过系统调用创建进程

#include <unistd.h>
pid_t fork(void);
返回值:自进程中返回0,父进程返回子进程id,出错返回-1

子进程继承了父进程的代码,他们两个都会执行return 0,所以就看到了执行两次

返回值

fork有两个返回值

#include <iostream>                                                                                                    
#include <unistd.h>  
using namespace std;  

int main()  
{           
    pid_t id = fork();  
    if(id == 0)         
    {            
        while(1)  
        {         
            cout << "child:" << getpid() << endl;  
            sleep(1);                                
        }          
    }    
    else if(id > 0)  
    {                
        while(1)  
        {         
            cout << "parent:" << getpid() << endl;  
            sleep(4);                               
        }            
    }     
    return 0;  
}            
 

父进程返回的是子进程的pid,子进程返回的是0,若返回的是小于0的数则代表失败

注意:fork创建的子进程会以父进程为模板初始化自己并共享父进程的代码和数据(内存中),但fork执行的代码是fork之后的代码,若当子进程或者父进程的数据发生改变,就会发生写实拷贝,即不在共享同一份,而是子进程会在内存中拷贝一份父进程的代码和数据并更改对应的值。

写实拷贝也就是通常,父子代码共享,父子再不写入时,数据也是共享的,当任意一方试图写入,便以写时拷贝的方式各自一份副本

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-fFrKn1Zz-1671610915803)(G:\Typora\图片保存\image-20221212123757851.png)]

fork调用失败的原因

  • 系统中有太多的进程

  • 实际用户的进程数超过了限制

  • 0
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值