v\:* {behavior:url(#default#VML);}
o\:* {behavior:url(#default#VML);}
w\:* {behavior:url(#default#VML);}
.shape {behavior:url(#default#VML);}
Normal
0
7.8 磅
0
2
false
false
false
EN-US
ZH-CN
X-NONE
MicrosoftInternetExplorer4
/* Style Definitions */
table.MsoNormalTable
{mso-style-name:普通表格;
mso-tstyle-rowband-size:0;
mso-tstyle-colband-size:0;
mso-style-noshow:yes;
mso-style-priority:99;
mso-style-qformat:yes;
mso-style-parent:"";
mso-padding-alt:0cm 5.4pt 0cm 5.4pt;
mso-para-margin:0cm;
mso-para-margin-bottom:.0001pt;
mso-pagination:widow-orphan;
font-size:10.0pt;
font-family:"Times New Roman","serif";}
进程存在于系统的内存之中,是操作系统可感知、可控制的动态实体。每个进程分为内核态(特权级0)和用户态(特权级3)两种级别。
Linux下一个进程在内存里有三部分的数据:“代码段”,就是存放了程序代码的数据,假如机器中有数个进程运行相同的一个程序,那么它们就可以使用相同的代码段。
“堆栈段”存放的就是子程序的返回地址、子程序的参数以及程序的局部变量。
“数据段”存放程序的全局变量,常数以及动态数据分配的数据空间(比如用malloc之类的函数取得的空间)。
系统如果同时运行数个相同的程序,它们之间就不能使用同一个堆栈段和数据段。
进程切换现场称为进程上下文,包含了一个进程所具有的全部信息:PCB、程序段和数据集。
PCB记录了进程的全部控制信息,一般包含:描述信息,控制信息,资源信息和CPU现场保护结构。
2.4.0版本中,每个task_struct结构占1680字节。
系统中的最大进程数由系统的物理内存大小决定
25.Linux进程的四要素
程序
PCB:进程创建时内核为其分配的一个核心数据结构,进程自身不能直接存取。
地址空间
系统堆栈空间:进程运行在核心态时使用的堆栈,和PCB连在一起,共8KB,其中PCB约占1000字节,系统堆栈空间约占7200字节。2.2内核中Linux进程个数有最大值限制(4092)。但2.4以后,系统中的进程个数受限于系统的物理内存数,即限定所有进程的PCB及系统堆栈(8K)占用的空间≤1/2的物理内存总和。例64M内存:进程数≤64M/2/8K=4K
PCB中的重要信息
身份信息:pid,uid,gid,euid,egid等;
状态信息:running, interruptible, non-interruptible,
stopped, zombie
调度信息:policy, priority, rt_priorty, need_reschedpolicy即进程的类别,分SCHED_FIFO,
SCHED_RR, SCHED_OTHER三种,前两种为实时进程,后一种为非实时进程
IPC信息:如定义对某些信号的处理等
家族信息:父进程、兄弟进程、子进程信息
时钟和定时信息
文件系统
存储管理
进程优先级,priority的值给出进程每次获取CPU后,可使用的时间(按jiffies计)。优先级可通过系统调用sys_setpriority()改变(kernel/sys.c)
rt_priority给出实时进程的优先级,rt_priority+1000给出进程每次获取CPU后,可使用的时间(同样按jiffies计)。实时进程的优先级可通过系统调用sys_sched_setscheduler()改变,不过实际的工作是由setscheduler()完成的,这两个函数均定义在kernel/sched.c中。
Counter:在轮转法(round robin)调度时表示进程当前还可运行多久。在进程开始运行时被赋为priority的值,以后每隔一个tick(时钟中断)递减1,减到0时引起新一轮调度。重新调度将从run-queue队列选出counter值最大的就绪进程获得CPU,因此counter起到了进程的动态优先级的作用(priority则是静态优先级)。
Policy:该进程的进程调度策略,可以通过系统调用sys_sched_setscheduler()更改(kernel/sched.c)。调度策略有:
SCHED_OTHER0非实时进程,基于优先权的轮转法(round robin)
SCHED_FIFO1实时进程,用先进先出算法
SCHED_RR2实时进程,用基于优先权的轮转法
进程队列指针struct task_struct *next_task, *prev_task;
所有进程(以PCB的形式)组成一个双向链表。next_task和prev_task就是链表的前后向指针。链表的头和尾都是init_task(即0号进程)。通过宏for_each_task可以很方便的搜索所有进程:
include/linux/sched.h
#define for_each_task(p) \
for (p = &init_task ; (p = p->next_task)
!= &init_task ; )
进程队列指针struct task_struct *p_opptr, *p_pptr;
struct task_struct *p_cptr, *p_ysptr, *p_osptr;
以上分别是指向原始父进程(original parent)、父进程(parent)、子进程(youngest child)及新老兄弟进程(younger
sibling, older sibling)的指针。相关的操作宏参见kernel/linux/sched.h。
进程约束
进程并发执行时,多个进程因竞争和共享系统中的有限资源而形成的相互约束关系。一般用进程互斥、同步实现方案解决。
Linux 2.4.21中,原子类型的定义和原子操作API都放在内核源码树的include/asm/atomic.h文件中,大部分使用汇编语言实现,因为c语言并不能实现这样的操作。
在所有支持的体系结构上原子类型atomic_t都保存一个int值。
Linux系统的进程通信
Linux系统提供了多种通信机制,包括
传统UNIX通信机制:信号信号(signal)属于Linux系统的低级通信,主要用于在进程之间传递控制信号,可用来实现进程互斥和同步问题。
管道管道(pipe)包括无名管道和有名管道,通过文件系统实现。
UNIX systemⅤIPC的通信机制:信号量;
消息队列;
共享内存;
基于网络、实现不同机器之间通信的套接字机制
信号是Unix系统中的最古老的进程间通信方式。它们用来向一个或多个进程发送异步事件信号。信号可以从键盘中断中产生,另外进程对虚拟内存的非法存取等系统错误环境下也会有信号产生。信号还被shell程序用来向其子进程发送任务控制命令。为区别于硬件中断,又称为软中断。使用kill命令(kill -l)可以列出系统中所有已经定义的信号。
Linux 2.2版以后将信号分为两类:
非实时信号:是UNIX使用的绝大部分传统信号,编号从1到31。
实时信号:由POSIX标准引入的一种信息号类型。目前未用,与前者的区别是同一种信号可以排队,以确保发送的多个信号被全部接收。
信号处理
信号保存到进程的一个数据结构中
相应位置为1表示收到相应的信号(向量有多少位,就有多少种信号),信号发送就是由内核在相应位置1
在适当的时机取出被设置的信号进行处理在系统调用返回时或中断返回时
在进程的数据结构中存有该进程对各信号的处理函数(是一个函数指针数组)(可用signal()来设置)缺省:一般为进程退出,如SIGFPE,浮点数异常,其缺省动作为引起core dump并退出
忽略:不处理此信号
自定义:用户自己定义的函数(用户态函数)
对信号的处理方式有三种,通过系统调用signal(sig,func)来设置:func=1时:忽略信号,但SIGSTOP和SIGKILL不能忽略。
func=0时:缺省处理,一般是终止进程,也可以停止进程或恢复进程运行
func为其他整数值时:自定义函数处理
信号的接收
task_struct中有一个signal域记录进程接收到的信号类型,共32位。
当某位置为1时,表示收到了某类信号。Linux不提供处理多个同类信号的方式。即进程无法区分它是收到了1个还是4个SIGCONT信号。
信号并非一产生就立刻处理,而是等到进程再次运行时才处理。
Linux通过存储在进程task_struct中的信息来实现信号。信号个数受到处理器字长的限制。32位字长的处理器最多可以有32个信号而64位处理器如Alpha AXP可以有最多64个信号。当前未处理的信号保存在signal域中,并带有保存在blocked中的被阻塞信号的屏蔽码。除了SIGSTOP和SIGKILL外,所有的信号都能被阻塞。
并不是系统中每个进程都可以向所有其它进程发送信号:只有核心和超级用户具有此权限。普通进程只能向具有相同uid和gid的进程或者在同一进程组中的进程发送信号。信号是通过设置task_struct结构中signal域里的某一位来产生的
信号并非一产生就立刻交给进程,而是必须等待到进程再次运行时才交给进程。每次进程从系统调用中退出前,它都会检查signal和blocked域,看是否有可以立刻发送的非阻塞信号。这看起来非常不可靠,但是系统中每个进程都在不停地进行系统调用,如向终端输出字符。当然进程可以选择去等待信号,此时进程将一直处于可中断状态直到信号出现。对当前不可阻塞信号的处理代码放置在sigaction结构中。
Linux是POSIX兼容的,所以当某个特定信号处理例程被调用时,进程可以设定哪个信号可以阻塞。这意味着可以在进程信号处理过程中改变blocked屏蔽码。当信号处理例程结束时,此blocked屏蔽码必须设置成原有值。因此,Linux添加了一个过程调用来进行整理工作,通过它来重新设置被发送信号进程调用栈中的原有blocked屏蔽码。对于同一时刻几个信号处理过程,Linux通过堆栈方式来优化其使用,每当一个处理过程退出时,下一个处理过程必须等到整理例程结束后才执行。
套接字是用来实现网络通信的机制。它可以实现数据的双向规模传递,是整个网络通信的基础。
26.管道的实现
在Linux中,管道通过指向同一个临时VFS inode的两个file数据结构来实现,此VFS inode指向内存中的一个物理页面
无名管道:ls|wc只存在于内存中由pipes()创建只限于有家族关系的进程之间
有名管道:和无名管道功能相同,只是不是内存文件,而是永久性的文件,外存文件。ls -l列出的p文件。有mknod创建(或mkfifo命令)管道文件。由两个进程调用open同时打开一个管道文件,然后分别调用read,write。任何两个进程之间都可以。
27.SYSTEMⅤ进程间通信
进程运行在各自的虚拟空间中,不能进行直接访问,只能通过系统提供的进程间通信机制(IPC)来完成。
SYSTEMⅤ的进程间通信包括:信号量:用于进程同步控制
消息队列:用于进程间传递格式化数据
共享内存:通过多个进程共同访问一块存储区来实现通信
Undo标志
信号量的使用可能产生死锁。例如,一个进程进入临界区时改变了信号量值,而离开临界区时由于运行失败等原因而没有改回信号量时,死锁将会发生。
Linux通过维护一组描述信号量数组变化的链表来防止该现象的发生。具体做法是当进行信号量操作时,将信号量变化值的负数存入进程semid_ds和task_struct中的undo结构中。如操作值为2时,则此信号量的undo调整值为-2。
共享内存允许若干进程共享主存中的某一个区域,且使该区域出现在多个进程的虚地址空间中。
每个新创建的共享内存区域由一个shmid_ds数据结构来表示。该数据结构包含:
ipc_perm结构
共享内存的大小
共享内存的最后操作时间
共享内存的最后操作者
28死锁
死锁是进程间的永久阻塞状态,若无外力作用,所有进程都将无法向前推进。
死锁产生的原因是资源竞争和进程推进顺序非法。
死锁产生的必要条件是互斥、请求和保持、不剥夺及环路等待。
解决死锁的方法有死锁的预防、避免、检测及解除。
29.进程调度
调度时机用户进程自愿放弃CPU,如执行sleep()系统调用;
系统调用中,需要等待时,直接调用schedule()进行调度;
系统调用、中断或异常处理完成后,返回到用户空间前,若当前进程的PCB中的need_resched = 1,则发生调度;
调度策略:基于进程的权值(weight,即动态优先级)实时进程:weight = 1000+rt_priority(>1000)
分时进程:weight = counter + 20 – nice(<1000)
其中:rt_priority:是实时进程的优先级
counter:进程还剩余的时间片值
nice:进程优先级的调整值(均在进程的PCB中标识)
30.线程
线程是同一个进程中独立的执行上下文——更简单一点地说,它们为单一进程提供了一种同时处理多件事情的方法,就像是进程是一个自行控制的微缩化了的多任务操作系统。同一线程组中的线程共享它们的全局变量并有相同的堆(heap),因此使用malloc给线程组中的一个线程分配的内存可以被该线程组中的其它线程读写。但是它们拥有不同的堆栈(它们的局部变量是不共享的)并可以同时在进程代码不同的地方运行。
线程是在一个进程内的基本调度单位。可以看作是一个执行流,拥有记录自己状态和运行现场的少量数据(栈段和上下文),但没有单独的代码段和数据段,而是与其他线程共享。因此,从这个意义上讲,一个进程可以看成一个虚拟处理器,多个线程运行在这个虚拟处理器上,且共享它的资源。
线程切换时,因现场保存工作量小,故提高了切换效率。
线程可以分为:用户级线程:用户级线程指不需要内核支持,在用户程序中实现的线程,包括线程的调度、同步都需要用户程序自己完成。
系统级线程:系统级线程需要内核的参与,由内核完成线程的调度并提供相应的系统调用,用户程序可以通过这些接口函数对线程进行一定的控制和管理。
线程也是动态实体,也应有数据结构记录线程的相关信息。线程也有生命周期,通过执行、就绪和等待状态来描述。同时线程间也存在通信与同步问题
进程和线程之间的联系与区别分为以下几个方面:调度:传统OS中进程是调度与资源分配的单位,进程其调度由系统完成;现代OS中线程是调度的单位,资源分配的单位仍然是进程,线程由用户或系统调用。
切换:进程调度要求完整记录上下文,系统开销较大;进程内线程切换不涉及资源保存及内存地址切换,系统开销较小。
通信:同一进程的线程间通信较容易;而进程间通信必须利用通信机制实现。
动态:两者都是动态实体,都有生命周期,整个生命周期都在不同状态之间转换。
进程和线程之间的最大区别是一个进程的所有线程共享同一个内存空间并共享系统定义的“资源”。这些资源包括打开的文件句柄(文件描述符)、共享内存、进程同步原语和当前目录。因为共享全局内存并且几乎不必分配新内存,所以创建线程比创建进程更简单更快。
Linux可以同时支持内核级线程和用户级线程。
Linux内核使用的观点——线程只是偶然的共享相同的全局内存空间的进程。
Linux中,内核级线程在描述、管理及调度等方面与进程没有严格区分,都当作进程来统一对待,用task_struct结构描述。
Linux内核级线程与进程的区别在于资源管理方面,线程可共享父进程的部分资源;创建进程用fork,创建线程用clone。可共享的资源包括:文件描述符、存储空间、进程同步原语和当前目录
Linux将内核级线程与进程一样管理和调度,使内核实现相对简单,系统管理开销小;但因使用了较大的task_struct结构描述线程,会造成一定空间浪费。
Linux提供用户级线程支持,用线程库可以方便地创建、调度、撤消线程。
31.在Linux中,每一个用户都可以访问4 GB线性虚拟内存空间。其中,0~3 GB是用户空间,用户可以直接对它进行访问;3~4 GB是内核空间,存放内核访问的代码和数据,用户态的程序不能访问。所有进程的3~4 GB虚拟空间都是一样的,有同样的页目录项,同样的页表,对应到同样的物理内存段。
32.对vm-area-struct数据结构的搜寻速度决定了处理缺页中断的效率,而所有vm-area-struct结构是通过一种AVL(Adelson-Velskii
and Landis)树结构连在一起的。如果无法找到vm-area-struct与此失效虚拟地址的对应关系,则系统认为此进程访问了非法虚拟地址。这时Linux将向进程发送SIGSEGV信号
虚拟段的组织:
v\:* {behavior:url(#default#VML);}
o\:* {behavior:url(#default#VML);}
w\:* {behavior:url(#default#VML);}
.shape {behavior:url(#default#VML);}
Normal
0
7.8 磅
0
2
false
false
false
EN-US
ZH-CN
X-NONE
MicrosoftInternetExplorer4
/* Style Definitions */
table.MsoNormalTable
{mso-style-name:普通表格;
mso-tstyle-rowband-size:0;
mso-tstyle-colband-size:0;
mso-style-noshow:yes;
mso-style-priority:99;
mso-style-qformat:yes;
mso-style-parent:"";
mso-padding-alt:0cm 5.4pt 0cm 5.4pt;
mso-para-margin:0cm;
mso-para-margin-bottom:.0001pt;
mso-pagination:widow-orphan;
font-size:10.5pt;
mso-bidi-font-size:11.0pt;
font-family:"Calibri","sans-serif";
mso-ascii-font-family:Calibri;
mso-ascii-theme-font:minor-latin;
mso-hansi-font-family:Calibri;
mso-hansi-theme-font:minor-latin;
mso-font-kerning:1.0pt;}