设为首页 加入收藏

TOP

【操作系统】2.进程和线程(一)
2023-08-26 21:10:20 】 浏览:65
Tags:程和线

1.操作系统的多进程图像

操作系统main函数中最后 if(!fork()) {init();} ,也就是main函数最后创建了第1个进程,init执行了shell(Windows)桌面。

操作系统管理和组织进程都使用PCB(Process Control Block),不同的程序的PCB放在不同的位置,用于记录该进程运行时的状态。操作系统对进程进行分类,例如等待执行的进程和等待某些事件完成的进程,例如等待磁盘读写。

 
  1. 新建态:系统完成创建进程的一系列工作。只能转换到就绪态
  2. 就绪态:拥有除CPU之外的其他所需的所有资源。当拥有CPU时就可以转换到运行态
  3. 运行态:用于CPU和所需的所有资源
    1. 当时间片到或者处理机被抢占了,就转换到就绪态;
    2. 当进程用“系统调用”的方式申请某种系统资源或者请求等待某个事件的发生,则进入阻塞态(主动)
  1. 阻塞态:没有所需要的资源。当所需要的资源得到分配时,进入就绪态(被动)
  2. 终止态:进程运行结束或者出现不可修复的错误时,由运行态转到终止态

进程切换的三个部分:队列操作+调度+切换

pCur.state = 'W';    // 启动磁盘读写,将当前进程设置为阻塞状态
schedule();         // 将pCur放到DiskWaitQueue

schedule()
{
    pNew = getNext(ReadyQueue);  // 从就绪队列找到下一个进程,调度函数算法非常复杂
    switch_to(pCur,pNew);  // 保存当前进程的现场,把下一个进程的现场恢复
}

把当前进程的现场保存到pCur中(PCB),把切换程序的pNew(PCB)读取到寄存器中

多个进程同时存在于内存的问题:不同进程的地址可能影响其他进程的代码,这可能导致其他进程的崩溃。操作系统需要维护一张映射表,将内存映射到实际的内存地址中,把不同的进程隔离开来保证进程的安全,下图中同样对内存100的操作分别映射到了内存地址780和内存地址1260。

但实际上 多进程之间可能存在合作关系,比如打印机进程需要读取word进程的内容来完成打印的工作,这时可以提交到共享缓冲区。但这里可能存在一个问题,因为进程1和进程2是交替进行的,可能进程1首先读取到空间7是空的,接下来切换到进程2也读取到空间7是空的,开始向空间7写入,接下来切换到进程1继续在这里写入,会导致写入缓冲区的内容是错误的。所以操作系统需要管理一个合理的进程推进顺序。

2.用户级线程

进程 = 资源(映射表) + 指令执行序列

线程是只切换指令,如PC和寄存器,而不切换映射表,这种切换保留了并发了优点,避免了进程切换的代价

举例说明,对于浏览器来说,可以用一个线程接收服务器数据,一个线程显示文本,一个线程处理图片,一个线程显示图片,它们不需要用多个映射表完全分离开,没有必要用多个进程完成这些工作。我们需要的工作主要就是下面看到的两个部分,创建Create线程进行工作处理,使用Yield跳转到另一个线程工作。

void WebExplorer(){
    char URL[] = "http://cms.hit.edu.cn";
    char buffer[1000];
    pthread_create(..., GetData, URL, buffer);
    pthread_create(..., Show, buffer);
 }

void GetData(char *URL, char *p) {...}
void Show(char *p) {...};

 线程切换的详细过程:每个线程都有自己的栈。线程1执行过程中,首先调用函数B(),保护现场,将上一段程序的帧指针和函数B完成后PC应指向的地址压入栈(参见【深入理解计算机系统】3.程序的机器级表示),接下来调用Yield()函数,保护现场,将之前的帧指针和Yield函数结束后的PC204压入栈,接下来Yield函数将当前栈指针1000保存在TCB1中,并将栈指针切换到TCB2的栈指针2000,完成了线程间的切换。接下来线程2的Yield使得栈指针回到1000处,继续上一个线程对应位置执行。下面给出了用户级线程的Create和Yield核心代码。

void Yield(){
    TCB1.esp = esp;  //Thread Control Block
    esp = TCB2.esp;
}
void ThreadCreate(A){
  TCB *tcb=malloc();   //申请空间保存TCB
  *stack=malloc();    //申请空间保存栈
  *stack = A;       //向栈中压入数据
  tcb.esp=stack;     //将栈和TCB建立联系
}

3.内核级线程

用户级线程存在的问题,用户级线程在请求下载数据的过程中,理想情况是下载了一些后跳转到显示文本的线程执行,但实际上内核级线程不知道这些事情,由于等待网卡IO会阻塞这个进程,最后导致浏览器没有实现我们需要的功能。

所以引入内核级线程,ThreadCreate是系统调用,会进入内核,Yield的调度由系统决定。

接下来看一下多核和多CPU,可以看到多核CPU只有一套MMU(内存映射),也就是多核心CPU在执行进程的时候,也需要切换内存映射再执行,只有多处理器才能并行运行多个进程。但这个时候内核级线程的优势就体现出来了,多核CPU可以并行的执行同一进程不同线程的代码,因为这些代码共用一套内存映射。

对于内核级线程,它与用户级线程的区别是

用户级线程在用户栈执行,多个用户级线程对应了多个用户栈,1个TCB(内核态)关联1个用户栈;

内核级线程在用户栈和内核栈都需要执行和调用函数,所以多内核级线程实际上对应了多套栈(包括用户栈和内核栈),1个TCB(内核态)关联1个用户栈和1个内核栈。

int中断指令会引起内核栈的切换,内核栈中记录了用户栈用户代码两部分内容。SS寄存器(栈顶段地址)和SP寄存器(偏移地址)的值,SS:SP是此时栈顶位置;PC记录了用户代码程序运行的代码位置,CS记录了用户代码段基址

 内核级线程的切换包含5个阶段

1.中断入口(进入切换):系统中断线程1从用户态进入内核态,用户态寄存器的值保存到内核栈

2.中断处理(引发切换):调用schedule函数,引起TCB切换。这里有可能启动磁盘读写或时钟中断,内核会调用schedule找到下一个要执行的TCB,然后用next指针指向这个TCB

3.内核栈切换(switch_to):把当前ESP寄存器放在current指向的TCB中,然后把next指向的esp赋给寄存器,完成内核栈指向地址的切换,现在ESP指向了下一个线程的TCB地址

4.中断返回(iret):把TCB存储的内核栈现场恢复出来

5.用户栈切换:切换回用户态PC指针还有对应的用户栈

4.内核级线程实现

首先从这段代码开始,main函数开始,首先遇到函数A,用户栈中压入A的返回地址(也就是B的初始地址),在A函数执行中遇到fork()函数,首先将系统调用号__NR_fork移入%eax寄存器,然后调用INT 0x80中断,执行这条指令时PC自动加1,此时PC指向下一行mov res,%eax。触发INT 0

首页 上一页 1 2 3 下一页 尾页 1/3/3
】【打印繁体】【投稿】【收藏】 【推荐】【举报】【评论】 【关闭】 【返回顶部
上一篇Linux文件操作 下一篇Centos-一文搞懂时区时钟配置和NT..

最新文章

热门文章

Hot 文章

Python

C 语言

C++基础

大数据基础

linux编程基础

C/C++面试题目