zoukankan      html  css  js  c++  java
  • 2.进程 20091221 18:33 121人阅读 评论(0) 收藏

            多道程序设计的操作系统中,进程被引入来代表程序的执行。进程如人,有生有死有父子,子进程会复制父进程的绝大多数信息,并在不同的地址空间运行。此处的“复制”只是概念中的复制,实际中为保持较好性能,往往采用写时复制技术。现代unix将执行流变成线程。它与进程的区别是共享进程(或称线程组领头线程)的地址空间,所以线程要注意共享的进程空间中的数据同步与互斥。POSIX线程库是用户级线程,而Linux用clone函数实现出的线程是内核级的。用户级线程切换快,但调度效率不高,一个线程阻塞则整个进程阻塞。内核级调度慢,但可以脱离进程进行调度,各有优缺。

           进程在程序的角度是什么?它是一组数据结构。在linux中,称为进程描述符。它是一个task_struct结构体。此结构存放数据多,比如它的state字段表示进程所处状态。改变时只需p->state=TASK_RUNNING之类语句即可。

           虽然进程与进程描述符有严格的一一对应关系,但用进程描述符描述进程太复杂,因为task_struct结构过于庞大,为此引入PID,即进程编号,为便于管理,引入PID后,内核管理一个pidmap_array位图用于记录已分配和未分配的PID。PID缺省上限32768,它正好可以放在一个页框内。Linux中线程由轻量级进程实现(通过clone函数),线程也有PID,Linux引入线程组的概念,属于同一个线程组的线程与领头线程拥有相同的PID。PID是进程描述符的一个成员。
           进程描述符在哪?如何找到?进程进入内核时,由于cs、ds换为GDT中内核模式,所以不能用用户模式下的栈,必须用内核栈。进程内核地址空间在0xc0000000以上,内核栈也必须在此空间内。因为进程描述符太大且有大量链表等数据结构纵横交错,所以此处也不能存储进程描述符。可以将进程描述符的成员thread_info结构存在此,后者再引出一个指针,指向进程描述符,而真正的进程描述符存在动态内存中。考虑将内核栈与thread_info用一个union定义绑定在一起,让此union起始地址为2^13的倍数,并将thread_info存在起始地址处,这样,只要对内核栈顶指针进行相应的与操作,便可轻松找到thread_info结构,从而根据它的成员task指针找到进程描述符。
           进程描述符散落在内核动态空间各处不便管理,于是用一个head_list类型的双向链表将它们横向串起,即head_list结构是进程描述符的成员。但这样的链表很长,同时不便搜索,所以将进程描述符中加入优先级成员,按优先级0-139分为140个双向链表,方便使用。
    为表现父子进程关系,进程描述符有parent,children字段,分别指向父与子,而各兄弟之间也用链表相连。这样,进程描述符就按各个方向被链表串起,这是典型的用复杂数据结构换取运行效率的例子。
           但这样还不够,开头的PID只是供用户使用,用户凭它找到进程描述符。显然顺序查找相当低效。因此引入hash表供查找,然而有时查找的是线程的PID,它期望找出一整个线程组的所有进程描述符,而一般hash表只能找出一个进程,此外,还有进程组查找等,所以得引入4个hash表,分别以PID为key,查找进程,tgid,pgrp,session,相应的hash表中若有多个描述符的,再通过链表串连。另外,以上仅处理了运行态的进程,还有阻塞进程等未有数据结构。引入一个等待队列,它是一个双向链表,链表头包含用于同步的自旋锁,链表元素即为指向进程描述符的指针。这就是复杂的进程描述符的大概布局。
           回到80x86,它有一个特殊的tss段,当cpu从用户态切换为内核态,会在tss中取内核栈的地址;通过in、out指令访问I/O端口时,cpu要访问tss中的端口权限位图。同样,这些信息没有存在tss寄存器本身,也是通过一层间接的数据结构GDT,指向真正的8字节的tss描述符。
           进程切换时,虽然进程有自己的地址空间,但cpu却必须保存当前相关寄存器的值及其它信息。这此称为硬件上下文。它们一部分在tss中,另一部分存在内核栈中。所以进程切换只可在内核态发生(它得从内核栈中取相应硬件上下文)。切换时把这些信息存在哪?进程描述符中有thread字段,用于保存硬件上下文。进程的切换大致分两步:1,切换页全局目录以装载新的地址空间;2,切换内核栈及硬件上下文。
           创建进程时,就是完成各种数据结构的初始化,最重要的当然是进程描述符。因为子进程从父进程复制,所以不成问题,但追本溯源,第一个进程的进程描述符哪来的?只能是静态分配的。它就是进程0。它初始化结束后,创建进程1后,就打开中断,不停执行halt指令。被它创建的进程1执行init()函数,完成内核初始化,之后调用execve()装入可执行程序init,此时,它从内核线程变为普通进程,负责监控整个操作系统所有进程的活动。
           进程代码执行完,释放内存、打开的文件。此时貌似已经结束,但重要的数据结构还在。这时它成为僵尸进程。引入僵尸进程是因为进程可能因出错而退出,若直接销毁进程描述符与内核栈,那么出错原因将不得而知,僵尸进程状态就是给父进程一个机会,让它可以取得进程的退出信息,以判断是否出错,最后再释放进程描述符与内核栈。

    版权声明:本文为博主原创文章,未经博主允许不得转载。

  • 相关阅读:
    广义表的创建和遍历
    dev c++ Boost库的安装
    NAT模式
    vmware桥接模式
    smb与samba
    利用Linux的Samba服务模拟NT域
    使用samba进行共享文件操作步骤
    安装chrome
    使用虚拟机上网第二步
    TCP协议三次握手过程分析
  • 原文地址:https://www.cnblogs.com/qqmomery/p/4700470.html
Copyright © 2011-2022 走看看