1.01k likes | 1.15k Views
第七章 进程/线程管理. 内容简介:. 进程概念、进程调度、进程通信和同步 线程定义、多线程同步及示例 多线程的编程方法. 目 录. 7.1 进程概念及组成 7.2 进程调度 7.3 进程运行和机制 7.4 进程通信与同步 7.5 线程概念及分类 7.6 线程基础 7.7 线程应用中的同步问题. 7.1 进程概念与组成. 7.1.1 进程概念. 进程 和 线程 是调度的基本单位 : 进程和线程的管理是操作系统中的核心部分。进程是静态概念。 线程描述进程内的执行 , 负责执行包含在进程的地址空间中的代码。.
E N D
内容简介: • 进程概念、进程调度、进程通信和同步 • 线程定义、多线程同步及示例 • 多线程的编程方法
目 录 7.1 进程概念及组成 7.2 进程调度 7.3 进程运行和机制 7.4 进程通信与同步 7.5 线程概念及分类 7.6 线程基础 7.7 线程应用中的同步问题
7.1 进程概念与组成 7.1.1 进程概念 进程和线程是调度的基本单位 : • 进程和线程的管理是操作系统中的核心部分。进程是静态概念。 • 线程描述进程内的执行, 负责执行包含在进程的地址空间中的代码。 • 进程和线程简介 20世纪60年代,进程(process)一词首先在麻省理工学院的MULTICS和IBM的CTSS/360系统中被引入。
独立性进程是系统中独立存在的实体,它可以拥有自己独立的资源,比如文件和设备描述符等。未经进程本身允许,其他进程不能访问到这些资源。独立性进程是系统中独立存在的实体,它可以拥有自己独立的资源,比如文件和设备描述符等。未经进程本身允许,其他进程不能访问到这些资源。 7.1.1 进程概念 • 进程的三个重要特性
7.1.1 进程概念 • 动态性 程序只是一个静态的指令集合,而进程是一 个正在系统中活动的指令集合在进程中加入了时间的概念。进程具有自己的生命周期和各种不同的状态。
并发性 7.1.1 进程概念 • 并发行由独立性和动态性衍生而来。若干个进程可以在单处理机状态上并发执行。 • 并发与并行: • 并行 指在同一时刻内,有多条指令在多个处理机上同时执行。 • 并发 指在同一时刻内只能有一条指令执行,但多个进程的指令被快速轮换执行,使得在宏观上具有多个进程同时执行的效果。
7.1.2 进程的组成 • 作为申请系统资源的基本单位,进程必须有一个对应的物理内存空间,要对其进行高效的管理,首先要用数据结构对空间进行描述。
7.1.2 进程的组成 • 进程编号 • 进程以进程号PID(process ID)作为标识。任何对进程的操作都要有相应的PID号。 • 每个进程都属于一个用户,进程要配备其所属的用户编号UID。 • 每个进程都属于多个用户组,所以进程还要配备其归属的用户组编号GID的数组 。 UID和GID都分4种, UID包括uid,euid,suid和fsuid, GID包括gid,egid,sgid和fsgid。 一般来说uid=euid=fsuid,gid=egid=fsgid。
进程上下文 7.1.2 进程的组成 • 运行进程的环境称为进程上下文(context) • 进程的上下文组成 • 进程控制块PCB包括进程的编号、状态、优先级以 及正文段和数据段中数据分布的大概情况。 • 正文段(text segment)存放该进程的可执行代码。 • 数据段(data segment)存放进程静态产生的数据结构 • 用户堆栈(stack)
进程表 进程表(process table) 将系统中所有的PCB块联系起来。 7.1.2 进程的组成 PCB PCB PCB …Test Data Data …Test… …Test
访问进程 7.1.2 进程的组成 Linux中的PCB块又称为task struct结构,Linux根据系统物理内存的大小限制已打开进程的总数目。系统每次访问一个进程时,内核根据PID在进程表中查找相应的进程PCB块(具体查找过程通过一个PID的hash表实现),再通过PCB块找到其对应的代码段与数据段,并进行操作。
7.2 进程调度 • 信号 • 进程状态 • 进程调度 内容简介:
7.2.1 信号 • Linux系统信号 • 信号主要用于通知进程异步事件的发生。 • 进程可以用kill或killpg系统调用向另一个进程发信号。 • 进程可以通过提供信号处理函数来取代对于任意信号的缺省反应,这种缺省反应一般都是终止进程。 • 信号发生时,内核中断当前的进程,进程执行处理函数来响应信号,结束后恢复正常的进程处理。
信号产生的条件: 7.2.1 信号 • 用户按下特定的键后,将向该终端前台进程组发送信号 • 硬件异常会产生信号:如被0除、无效内存引用等。 • kill(2)系统调用允许进程向其他进程发送任意信号 • kill(1)命令允许用户向进程发送任意信号。 • 软件设置的条件,如SIGALARM。 在如下几种情况下才有可能处理 进程的信号: • 每次进程从系统调用中退出时 • 内核在调度程序中选择执行该进程时 • 如果有任何一个未被阻塞的信号发出,内核就根据sigaction结构数组中的信息进行处理。
基本函数 7.2.1 信号 • 其中signo是系统信号表中的信号名;func的值是SIG_IGN或SIG_DFL或者是接到此信号后需要调用的函数地址。 • 当func为SIG_IGN时则向内核表示忽略此信号。 • 当func为SIG_DFL表示接到此信号的动作是系统默 认动作。 • 当func为函数地址时为捕捉信号。出错则返回。SIG_ERR成功返回信号处理配置。 • 捕捉信号 此函数是信号处理程序或者信号捕捉函数,决定系统对信号的响应 void (*signal (int signo,void (*func)(int)))(int)
7.2.1 信号 • 发送信号 • kill int kill(pid_t pid,int sig);向其他进程发送信号 • pid>0是将信号发送到PID为pid的进程 • pid=0信号发送到与发送进程在同一进程组的进程 • pid<-1将信号发送到进程组ID等于-pid的进程 • pid=-1Linux发到进程表中除第一个进程外的进程 • sig=0时,不发送任何信号,但仍然执行错误检查
7.2.1 信号 • raise ; Int raise(int sig);向当前进程发送信号,等价于kill(getpid(),sig)即发信号到当前进程。成功返回为0,出错为-1。 • alarm;unsigned int alarm(unsigned int seconds);此函数用来设置一个时间值(闹钟时间),当所设置的值被超过后,产生SIGALRM信号,默认动作是终止进程。
7.2.1 信号 • rause;int pause(void);可以使进程挂起,直到捕捉到一个信号。 • sleep;unsigned int sleep(unsigned int seconds);此函数挂起调用中的进程,直到过了预定时间或者是收到一个信号并从信号处理程序返回。
7.2.1 信号 例:程序执行2秒打印hello字符串 #include <unistd.h> #include <signal.h> void handler(){ printf(“hello\n”); }
main() { int i; signal(SIGALRM,handler); alarm(2); for(I=1;I<4;I++){ printf(“sleep %d \n”,I); sleep(1); } } 执行结果: sleep 1 sleep 2 hello sleep 3
7.2.2 进程状态 • 进程是一个动态的实体,故而它是有生命的。一般来说,所有进程都要经历以下3种状态。 • 就绪(ready)态 • 阻塞(blocked)态 • 运行态
7.2.2 进程状态 • 就绪态指进程已经获得所有所需的其他资源,并正在申请 处理机资源,准备开始运行。 • 阻塞态又称休眠状态或者等待状态。指进程因为需要等待 所需资源而放弃处理机,或者进程本不拥有处理 机,且其他资源也没有满足,从而即使得到处理机 资源也不能开始运行。 • 运行态指进程得到了处理机,不需要等待其他任何资源, 正在执行的状态, 此时进程才可使用申请的资源
RUNNING 7.2.2 进程状态 正在运行,或者在就绪队列中等待运行的进程。也就是上面提到的运行态和就绪态进程的综合。一个进程处于RUNNING状态,并不代表它一定在被执行。某个特定时刻,这些处于RUNNING状态的进程之中,只有一个能够得到处理机,而其他进程必须在一个就绪队列中等待。
UNINTERRUPTABLE 7.2.2 进程状态 不可中断阻塞状态。处于这种状态的进程正在等待队列中,当资源有效时,可由操作系统进行唤醒,否则,将一直处于等待状态。 • INTERRUPTABLE 可中断阻塞状态。与不可中断阻塞状态一样,处于这种状态的进程也在等待队列中,当资源有效时,可以由操作系统进行唤醒。
STOPPED 7.2.2 进程状态 挂起状态。进程被暂停,需要通过其他进程的信号才能被唤醒。 导致这种状态的原因有两种: 1.是受到了相关信号(SIGSTOP、STP、 SIGTTIN 或SIGTTOU)的反应。 2.是受到父进程ptrace调用的控制,而暂 时将处理机交给控制进程。
ZOMBIE 7.2.2 进程状态 僵尸状态。表示进程结束但尚未消亡的一种状态。此时进程已经结束运行并释放大部分资源,但尚未释放进程控制块。
7.2.3 进程调度 • 简介 1. 调度程序(scheduler)用来实现进程状态之间的转 换。schedule()是一个怪异的函数, 它的调用和返回不在同一个进程中。 2. 用户进程由fork()系统调用实现。fork()创建一个新的进程,继承父进程的现有资源,初始化进程时钟、信号、时间等数据。完成子进程初始化后,父进程将它挂到就绪队列,返回子进程的PID。
进程调度 7.2.3 进程调度 1.进程创建时的状态为UNINTERRUPTIBLE,在fork()结束前被父进程唤醒后,变为RUNNING。 2. RUNNING状态的进程被移到就绪队列中,适当时候由schedule()按处理机调度算法选中,获得处理机。 3.获得处理机若申请不到某个资源,则调用sleep()进行休眠,其PCB挂到相应的等待队列,状态变为UNINTERRUPTIBLE或INTERRUPTIBLE。sleep()将调用schedule()函数把休眠进程释放的处理机分配给就绪队列中的某个进程。
7.2.3 进程调度 4.状态为INTERRUPTIBLE的休眠进程当它申请的资源有效时被唤醒,也可由信号或定时中断唤醒。而状态为UNINTERRUPTIBLE的休眠进程只有当它申请的资源有效时被唤醒,不能被信号和定时中断唤醒。唤醒后,进程状态改为RUNNING,并进入就绪队列。 5.进程执行系统调用exit()或收到外部的杀死进程信号SIG_KILL时,进程状态变为ZOMBIE,释放所申请资源。同时启动schedule()把处理机分配给就绪队列中其他进程。
7.2.3 进程调度 6.若进程通过系统调用设置了跟踪标志位,则在系统用返回前,进入跟踪状态,进程状态变为STOPPED,处理机分配给就绪队列中其他进程。只有通过其他进程发送SIG_KILL信号或继续信号SIG_CONT,才能把STOPPED进程唤醒,重新进入就绪队列。 对每一个进程,其PCB块中都可以记录一种调度策略。进程调度算法可采用先进先出算法(FIFO或轮转法(round-robin)若采用Linux的轮转法,当时间片到时(10ms的整数倍),由时钟中断触发,引起新一轮调度,把当前进程挂到就绪队列队尾。
7.3 进程运行和控制 • 父子进程 Linux中除了0号进程是启动时由系统创建,其余进程都是由其他进程自行创建的。如果进程A是进程B的间接父进程,则A称做B的祖先,B为A的后代。在数据结构上,父进程PCB中的指针p_cptr指向最近创建的一个子进程的PCB块,而每个子进程PCB中的指针p_pptr都指向其父进程的PCB块。
父子进程关系 父进程 p_pptr p_pptr p_cptr p_pptr 子进程 最新的 子进程 最老的 子进程 p_osptr p_osptr p_ysptr p_ysptr
fork() 7.3 进程运行和控制 1. 创建新进程的调用是fork(),当进程A调用fork()生成进程B时,fork()函数同时在A和B两个进程中返回。其中,父进程A里的fork()返回了子进程的PID,而子进程B里的fork()返回0 系统启动时,自行创建了0号进程,其所运 行的代码是init_task()函数。该进程的作用 是作为一切其他进程的父进程。
7.3 进程运行和控制 2. 父进程只复制了自己的PCB块,而代码段、数据段、用户堆栈内存空间并没有复制一份,而是与子进程共享。只有当子进程在运行中出现写操作时,才会产生中断,并为子进程分配内存空间.由于父进程的PCB和子进程的一样,所以在PCB中所记录的父进程占有的资源,也是与子进程共享使用的。
vfork() 7.3 进程运行和控制 希望父进程可以等待子进程运行结束后再继续执行。调用vfork()可以使在子进程创建 后,随即向父进程发送SIG_STOP信号,使父进程进入挂起状态,直到子进程发送信号表示其已经结束为止。
7.3 进程运行和控制 • execve() 如果系统中只提供vfork()调用,那么整个操作系统的所有进程就都只能运行同一个程序了,因为其代码段都是复制或者共享的。Linux为了创建进程运行新的程序,采用函数execve(),其支持在新的进程创建后,动态装入新的可执行文件作为自己新的代码段。并且execve()支持多种可执行文件的格式。
7.3 进程运行和控制 • 示意性代码 kernal/exit.c static inline void __exit mm(struct taskstruct *tsk) {… wake_uo(&tsk->mm->vforkwait);//唤醒父进程 …}
7.3 进程运行和控制 kernal/fork.c int do_fork(unsigned long clone_flags,unsigned long usp,struct pt_regs *regs) {… #ifdef NO_MM if(clone_flags&CLONE_WAIT){ sleep_on(&p->mm->vforkwait);//父进程进入睡眠 #endif/*NO——MM*/ …}
7.3 进程运行和控制 fs/exec.c int do_execv(char *filename,char **argv,char **envp,struct pt_regs *regs) {… #ifdef NO_MM*/ wake_up(t->mm->vforkwait);//唤醒父进程 #endif/* NO_MM */ …}
7.4.1 进程通讯 7.4 进程通讯与同步 • 用户态进程间处于并发状态,为了协调进程的运行,需要实现进程之间通信的机制。 在Linux中,进程间通信有以下几种方法: 1.管道机制 2.先进先出(FIFO)机制 3.IPC机制
管道机制 7.4.1 进程通讯 管道是一种在进程之间单向流动数据的结构。源进程向管道写数据,而内核会自动将这些数据引导向目标进程。虽然通过pipe()调用会产生两个描述符(写管道和读管道),但是在写之前必须关闭读管道,反之亦然。在Linux中,使用一个管道的同时还可以使用另一个管道。 该机制的最大缺点是不能由多个进程共享,除非此管道为这些进程共同的祖先所创建,为了解决这个问题,Linux中引入了FIFO机制(有名管道)。
先进先出(FIFO)机制 7.4.1 进程通讯 • FIFO为“first in,first out”的简写,指一个在磁盘上的文件,它可以被所有进程所共享。 • FIFO与一般文件不同,它还使用了内核中的缓冲区,所以在效率上要比一般共享文件快得多。 • FIFO和管道都可以使用read()和write()调用来进行读写操作。
IPC机制 7.4.1 进程通讯 • IPC--interprocess communication • 它包含一系列系统调用,允许用户态进程通过信号量进行同步,向其他进程发消息,还可以与其他进程共享一块内存空间。 • IPC资源:1.消息队列2.共享内存3.信号量
消息队列 7.4.1 进程通讯 消息队列是由内核创建并维护的一个数据结构,它是有标识的任何具有足够权限的进程都可以向消息队列中放置一个消息,同样,任何具有足够权限的进程都可以从中读取一个消息。
共享内存 7.4.1 进程通讯 • 共享内存区是这几种进程间通信方式中最快的一种:速度快,可传递的信息量大。 • 它是通过将一段内存区映射到一个进程的地址空间来实现,进程间通信不再涉及到内核。 • 内核必须建立允许各个进程共享该内存区的内存映射关系,然后一直管理该内存区,有效地保证它能同步、有序且没有死锁。 共享内存实现过程: 1.服务器取得访问该共享内存区的权限 2.服务器从输入文件读取数据到该共享内存区 3.服务器读入数据完毕时,通知用户进程 4.用户从该共享内存区读出这些数据并输出
7.4.1 进程通讯 • 信号量 • 信号量并不是一种IPC机制,是提供不同进程间或一给定进程的不同线程间同步的一种手段。 • 信号量主要包括以下几种类型: • 二值信号量 • 计数信号量 • 计数信号量集
7.4.1 进程通讯 • 二值信号量:其值为0或1。资源被锁住而不可用时,值为0;资源可用时,值为1 • 计数信号量:其值在0和某个限制值之间。它的值一般是可用的资源数 • 计数信号量集:由一个或多个信号量构成的一个集合,其中每一个都是计数信号量。每个集合的信号量数都有一个限制值,一般在25个以上