西北农林科技大学操作系统实验一------关于Linux系统的内存管理

文章目录

  • Linux进程与线程定义
  • 进程描述符
    • 进程基本信息块
    • 与调度有关的字段
    • Linux系统状态的变迁
  • Linux系统进程的创建和中止
    • Linux系统进程的创建
    • Linux 系统进程的终止
  • Linux系统的进程等待与唤醒
    • Linux 系统进程的等待
    • Linux 系统进程的唤醒
  • Linux线程的实现
    • 内核线程

Linux进程与线程定义

进程是处于执行期的程序,它是分配系统资源和调度的实体。
进程包括可执行的程序代码、打开的文件、挂起的信号、内核数据、地址空间、处理机状态、一个或多个可执行的线程等。Linux 内核通常将进程称为任务。
线程是进程活动的对象。每个线程都有一个独立的程序计数器、堆栈和一组寄存器。在Linux 系统中,对线程进程并不特别地区分。对Linux 而言,线程是一种特殊的进程。线程被视为一个与其他进程共享某些资源的进程。

进程描述符

Linux系统中描述进程的数据结构称为进程描述符(processdescriptor)。Linux系统的进程描述符是task struct类型结构,它包含了一个具体进程的所有状态,完整地描述一个正在执行的程序。进程描述符的内容是非常丰富的,它不仅包括进程属性字段,还包括一些指向其他数据结构的指针字段。
西北农林科技大学操作系统实验一------关于Linux系统的内存管理_第1张图片

C语言定义
系统给每个状态分配了一个字母缩写“RSDTtZXxKWP”,对应关系如下所示。

#define TASK_RUNNING 1//可运行状态:1:运行,存储管理地址映射机构正在指向该进程0:就绪,正在就绪队列上等待
#define TASK_INTERRUPTIBLE 1//可中断的等待状态
#define TASK_UNNTERRUPTIBLE 1//可中断的等待状态
#define TASK_STOPPED 1//进程停止执行
#define __TASK_TRACED        8
#define EXIT_ZOMBIE        16
#define EXIT_DEAD        32
#define TASK_DEAD        64
#define TASK_WAKEKILL        128
#define TASK_WAKING        256
#define TASK_PARKED        512
#define TASK_STATE_MAX        1024
#define TASK_STATE_TO_CHAR_STR "RSDTtZXxKWP"
/*①可运行状态(TASK RUNNING)。
i 运行;该进程正在CPU上运行,存储管理地址映射机构正指向该进程。
就绪:该进程正在运行队列上等待运行。
②可中断的等待状态(TASKINTERRUPTIBLE)。进程正在等待某一事件的发生(如某一硬件中断或一个信号),它处于挂起(或称睡眠)状态。处于此状态的进程当接收到信号时会被唤醒,检查并处理信号。当检查到正是等待的事件(条件为真)时,移出等待该事件的队列,转变
为就绪状态。
③不可中断的等待状态(TASK_UNNTERRUPTIBLE)。此状态与可中断的等待状态类似不同的是,信号传递到睡眠进程并不能改变其状态。进程必须等待,直到使它不能中断的那个事件结束。这种状态很有用,常在一些特定的情况下使用。例如,一个进程打开一个设备文件、其相应的设备驱动程序开始工作。设备驱动程序在探测所需后动的硬件设备时,设置为这种状态。在探测完成之前设备驱动程序不能被中断,否则设备会处于不可预知的状态。
④暂停状态(TASK STOPPED)。进程停止执行。当进程接收到SIGSTOP、SIGTSTP
SIGTTIN、SIGTTOU等信号时,会发生这种状态。*/

这里有对进程描述符详尽的定义
task_struct的定义与注释

进程标识符进程描述符之间有严格的对应关系 在Iinux系统中,所有的进程描还衬用个结构数组来定义,每一个进程描述符是其中的个结构系统在为进程创建进程描述符时,村该结构的序号作为进程标识符。PID是顺序编号的,新创建进程的 PID通常是前一个进程的加1。PID的值有一个上限,当内核使用的 pID达到这个上限时,必须循环使用已闲置的小的号。在32位体系结构中,最大的 PID号为32767(pIDMAXDEFAUUT-1);在64位体系结构中、最大的PID号为4194303。

进程基本信息块

每个进程都有一个进程基本信息块。在进程描述符的thread_info字段中包含了指向该结构的指针。thread info位于进程内核栈的尾端,在该结构的TASK域存放的是指向该进程的task struct的指针。thread_info结构是为了快速找到进程描述符而设置的。

与调度有关的字段

Linux系统采用优先调度策略,每个进程都有一个优先级。当CPU空闲时,进程调度程序选一个优先级最高的进程去运行。Linux2.6版本为了实现灵活、有效的进程调度,对处于就绪状态的进程按优先级的高低组织成多个进程链表。系统给每个进程确定一个优先级 对应一个优先数), 其值为k(取值范围为0~139)。系统将处于同一优先级的进程组成一个队列。每种优先级对应一个不同的链表。这样,Linux系统中可运行进程链表最多可有140个。在task struct中包含listhead 类型的字段 run list,该字段的信息与进程调度密切相关。

Linux系统状态的变迁

Linux 系统提供创建一个新进程的机制,当系统或用户需要创建一个新进程时,调用 fork()系统调用,被创建的新进程被置为就绪状态(TASKRUNNING)。当调度时机到来时,进程调度程序从进程运行队列中选择优先级最高的进程,设置为可运行状态,将其投入运行。正在CPU上运行的进程,当其优先级低于处于就绪状态的某一个进程的优先级时,它被抢占而被迫让出CPU 的控制权,此时,该进程的状态转为就绪状态。当正在运行的进程因等待某一事件而暂时不能运行下去时,进入等待状态(一般设置为TASKINTERRUPTIBLE状态),进入相应的等待队列。当某个进程等待的原因撤销时,该进程被唤醒,从其等待队列中移出,进入就绪队列。当正在运行的进程完成其任务时,通过exit()系统调用终止自己。

进程创建
进程调度
被抢占
服务请求
服务完成
创建TASK_RUNNING
就绪TASK_RUNNING
运行TASK_RUNNING
等待TASK_INTERRUPTIBLE或TASK_UMNTERRUPTIBLE
运行中止

Linux系统进程的创建和中止

进程控制负责进程状态的变化,Linux系统提供进程创建与撤销、进程等待与唤醒等功能。

Linux系统进程的创建

  1. 写时拷贝

传统的Linux系统用fork()系统调用创建一个进程,它是通过子进程复制父进程所拥有的资源的方法来实现的,这种进程创建方法慢且效率低。因为,子进程需要拷贝父进程的整个地址空间。实际上子进程几乎不用读或修改父进程拥有的所有资源。在很多情况下,子进程立即调用exe()系统调用,执行另一个新文件,并清除父进程拷贝过来的地址空间。
Linux的fork()使用写时拷贝(copy-on-write)技术对此问题进行了优化。写时拷贝是一种可以推迟,甚至免除数据拷贝的一种技术在创建新进程时内核并不复制父进程的整个地址空间, 而是让父进程和子进程共享同一拷贝(以读方式共享)。只有当一方真正需要写人时,数据才被复制,这时,父、子进程才拥有各自的拷贝。换句话说,资源的复制只有在需要写入时才进行。采用这种技术后,fork(后立即调用exec()函数,父进程的地址空间是不会被复制的。

Linux下C语言创建子进程实例

#include
#include
#include
#include

int main()
{
   pid_t pid;
   char *message;
   int n;
   pid = fork();/*创建子进程*/
   return 0;
}
  1. 进程创建过程

Linux系统为实现进程创建功能,提供 fork()和 clone()系统调用,fork()用来创建一般进程, clone()用来创建轻量级进程(线程)。Linux系统通过clone()函数来实现 fork()功能,该调用通过一系列的参数标志来指明父、子进程需要共享的资源
clone(函数的主要工作是调用do fork())。do fork()完成了创建进程的大部分工作。在do fork()中为新创建的进程分配新的PID(通过查找 pidmap_array 位图),根据父进程中设置的若干标志进行相应处理(如子进程是否被跟踪等),其中,最重要的是调用copy_process)函数来创建进程描述符以及子进程执行所需要的所有其他的内核数据结构。copy_process/函数的主要工作如下。
调用dup_task_struct()为新进程创建内核栈、thread_info结构和 task struct。此时,子进程和父进程的描述符是完全相同的。
子进程描述符内的一些成员被清0或被设置为初始值,而大多数数据是共享的。
检查系统中的进程数量是否超过了max threads变量的值。
子进程的状态被设置为TASK_UNINTERRUPTIBLE,以保证它不会被投入运行。
调用copy_flags()以更新flags成员,将表明进程是否拥有超级用户权限的PFSUPERPRIV 标志清0,设置表明进程还没有调用exec()函数的PF_FPRKNOEXEC标志。
将新进程的PID存入进程描述符的pid字段。
依传递给clone()的参数标志,拷贝或共享打开的文件、文件系统信息、信号处理函数、进程地址空间和命名空间等。
父、子进程平分剩余的时间片。
扫尾工作并返回一个指向子进程的指针。从copyprocess()函数返回时,若成功,新创建的子进程被唤醒并让其投入运行。内核有意选择子进程先运行,因为一般子进程会马上调用execO函数,可免除写时拷贝的开销。

Linux 系统进程的终止

  1. Linux系统提供exit()系统调用以终止某一个进程。所有进程的终止都是由do_exec()函数来处理的。do_exec()函数的主要工作如下。

①将task struct中的fags字段设置为PE_EXECING标志,以表示该进程正在被删除。
②分别调用exit_mm()、 exit_sem()、exit_files()、exit_fs()、exit_namespace()和exit_thread()
函数从进程描述符中分离出与分而。信号量、文件系统、打开文件描述符、命名空间相关的数据结构。若无别的进程共享这些数据结构,则彻底释放它们。
③将进程描述符的exit_code字段设置为进程的终止代码,该终止代码可供父进程随时检索。
④调用exit_notify()向父进程发信号,更新父进程和子进程的亲属关系,并将进程状态设置
为TASK_ZOMBIE。
⑤最后调用schedule()进程调度程序,调另一个进程运行。
进程终止后,此进程处于僵死状态,但系统还保留了它的进程描述符。Linux 系统与 Unix系统一样,将进程终止时所需做的清理工作进程描述符的删除分为两步执行。只有父进程发出了与被终止进程相关的 wait()系统调用后,子进程的task_struct结构才能释放。wait()系统调用在最后调用 release_task()。release_task()调用put_task_struct(),以释放进程内核栈和 thread_info()结构所占用的页,并释放task_struct()所占用的空间。全此,进程描述符和进程占有的所有资源就全部释放了。

Linux系统的进程等待与唤醒

运行进程需要等待某一事件时会转变为等待状态,当等待的事件来到时进程会被唤醒。Linux系统提供进程等待和进程唤醒的功能。
Linux 系统设置了TASK_INTERRUPTIBLE和 TASK_UNINTERRUPTIBLE两种不同的进程等待状态。这两种等待状态的唯一区别是,处于TASK_INTERRUPTIBLE状态的进程如果接收到一个信号会被提前唤醒并响应该信号,而处于TASK_UNINTERRUPTIBLE状态的进程会忽略信号。

Linux 系统进程的等待

Linux 系统设置了等待队列,进程等待实质上是通过加入某一等待队列来实现的。等待队列是由等待某一事件发生的进程组成的进程链表,Linux 内核用wake_queue head_t表示等待队列。进程需要等待某一事件时,加入到相应的等待队列并设置成不可执行的状态。当与等待队列相关的事件发生时,队列上的进程会被唤醒。
进程等待的主要步骤如下。
①调用declre _wait queue()创建一个等待队列的元素。
②调用add waitqueue()将该元素加入到等待队列。
③将进程设置为TASK INTERRUPTIBLE状态或TASK UNINTERRUPTIBLE状态。
④转进程调度程序schedule()。

Linux 系统进程的唤醒

由于进程等待状态有TASK INTERRUPTIBLE和TASK UNINTERRUPTIBLE两种,所以当信号来到或发生所等待的事件时都会唤醒进程。
进程唤醒的主要工作如下。
①当进程状态设置为TASK_INTERRUPTIBLE时,由信号唤醒进程,这是所谓的伪唤醒(不是直接由所等待的事件唤醒),因此需要检查并处理信号。
②若检查条件为真(所等待的事件发生),转④;若条件不为真,转进程调度 schdule()。
③当进程被唤醒时(因事件发生),检查条件是否为真,若为真,转④;否则,转进程调度schdule()。
④当条件满足时,进程状态设置为TASKRUNNING,并调用remove_wait_queue()将该进程移出等持队列。
⑤调用rry_to_wait up),该函数将进程状态设置为TASK RUNNING,再调用activake_task0 将此进程加人到可执行队列。若被唤醒进程的优先级比当前正在运行的进程的优先级高,则设置ned resched标志。

Linux线程的实现

Linux 系统将所有线程当作进程来实现,线程仅仅被看作是一个与其他进程共享某些资源的进程所以Linux 内核并没有针对线程的数据结构和特别的调度算法,每个线程拥有自己的进程描述符task_struct。在Linux系统中,线程只是一种进程间共享资源的手段。
例如,若有一个进程包含4个线程,在提供专门支持线程的系统中,进程拥有进程描述符,线程拥有线程描述符。在进程描述符中描述地址空间、打开的文件与共享的资源。在Linux系统中则创建4个进程,并分配4个task_struct结构。建立这4个进程时指明它们共享的资源即可。线程的创建与普通进程的创建类似,只不过在调用clone时要传递一些参数特征来指明需共享的资源

参数标志 含 义
CLONE_FILES 父子进程共享打开的软件
CLONE_FS 父子进程共享文件打开系统信息
CLONE_NEWNS 为子进程创建新的命名空间
CLONE_PARENT 指定子进程与父进程拥有同一个进程

内核线程

(1)内核线程在后台执行一些操作,它是独立运行在内核空间的标准进程。其特点是∶
①没有独立的地址空间;
②只在内核空间运行,从不切换到用户空间;
③可以被调度、被抢占。内核线程只能由其他内核线程来创建,新的内核线程通过clone(调用创建,在调用时需要传递特定的0as参数。一般情况下,内核线程会将它在创建时对应的函数永远执行下去(除非系统重启)。该函数通常执行一个循环,当满足特定的条件时,被唤醒并执行;完成了相应的任务后又会自行休眠。Linux系统的内核线程所完成的任务包括刷新磁盘高速缓存、换出不用的页面、维护网络连接、处理与高级电源管理(APM)相关的事件等。
(2)进程优先级
进程的优先级反映了进程需要运行的紧迫程度。Linux内核提供两组独立的优先级范围。种是nice值,范围为-20-19,默认值为0。nice之越大优先级越低。第二种是实时优先级范围为从0-99。任何实时进程的优先级都高于普通进程的优先级。
每个进程都有自己的静态优先级和动态优先级。进程的这两种优先级与进程调度密切Linux内核通过nice、进程的特征和计算规则来计算进程的优先级、并作为调度的依据。
(3)进程标识
进程标识符(proessID,PID)用来标识一个进程。PID存放在进程描述符的 pid字段"在进程描述符中还包含另外儿个表示不同类型的PID字段,如表4.2所示。

字段名 说明
pid 进程的PID
tgid 线程组领头进程的pid
pgrp 进程组领头进程的PID
session 会话领头进程的PID

这是我对Linux线程进程的知识的梳理,希望大家多多批评指正。
嘻嘻

西北农林科技大学操作系统实验一------关于Linux系统的内存管理_第2张图片

你可能感兴趣的:(Ubuntu,Linux)