linux操作系统-3进程管理(2)

进程调度

进程调度必须防止进程饿死,即低优先级的进程始终得不到运行。

1)进程的切换

来实现多个进程共享CPU。另外,当进程需要睡眠时,也必须切换进程。进程的切换主要包括几个内容:

1、硬件上下文的切换,主要是寄存器,包括控制寄存器和通用寄存器。

2、任务状态段的切换

i386提供的一种数据结构。为了简化设计,linux内核实现了每一个逻辑CPU一个任务状态段。内核的堆栈地址是保存在任务状态段里,当从用户空间向内核切换时,硬件自动从任务状态段中取出内核堆栈地址并初始化堆栈指针寄存器。另一方面,不同的进程使用不同内核堆栈,所以当进程切换时,必须切换任务状态段中的堆栈指针。

3、内核堆栈

进程的切换一定是发生在内核状态下,必须切换内核堆栈指针寄存器,是用软件的方法实现的,linux内核定义一个宏,switch_to,实现内核堆栈指针寄存器的切换。保存当前的堆栈指针寄存器内容,把下一个进程的堆栈指针取出并复制到堆栈指针寄存器。

4)浮点数寄存器

内核在三种情况下会保存浮点数寄存器:任务切换、处理信号、内核自己需要使用浮点数寄存器。

2)进程的调度策略

LINUX进程调度采用策略来区分进程。定义了几种进程:实时进程、交互式进程、批处理进程。在task_struct中定义了policy字段,指定该进程的调度策略。

Policy可以等于4个值:

SCHED_NORMAL 基于时间片机制。进程具体可以表现为交互式进程或批处理进程。

SCHED_FIFO 先进先出的实时进程。

SCHED_RR 循环执行的实时进程。

SCHED_BATCH 批处理进程。

调度策略只是比较大概的进程分类,实际调度时必须映射到调度的优先级。LINUX定义了140个优先级。0优先级最高,139最低。总是实时进程优先运行。只有当没有处于运行状态的实时进程时,交互式进程和批处理进程才可以得到调度。

时间片是进程实际被分配到的运行时间,以tick作为单位。时钟中断每发生一次,算作一个tick。每当中断发生时,内核就在当前进程的时间片里减1.如果时间片变成了1,内核就重新调度进程。实际要复杂多了。

实进程包括FIFO和RR两种。FIFO先进先出,当一个FIFO运行时,如果没有比它优先级更高的进程可以运行,即使FIFO进程时间片运行完也不会让CPU,而是再获得一个新的时间片。RR是循环调度运行的进程,运行完时间片后,需要向同一优先级的其他RR进程让出CPU。

交互式进程和批处理进程的优先级是动态变化的。内核用一个公式计算出进程新的优先级。公式输入参数是进程的静态优先级和最近的过去进程睡眠时间。

3)进程调度的触发和进程切换时机

进程调度的核心任务由函数schedule处理,主要是选择并切换到下一个合适的进程。在内核里,函数scheduler_tick处理由时钟中断触发的调度,是当前进程打标记TIF_NEED_RESCHED.一定要当进程进入合适的时候,就是已经释放了共享资源后并处于安全的时候,才可以调用函数schedule做真正的进程调度,否则可能会导致系统的死锁。

函数try_to_wake_up用于唤醒处理。唤醒优先级高的进程,并且需要在当前CPU运行,就会设置当前进程的TIF_NEED_RESCHED标志位。另外,创建新的进程和exec时都可能引发进程的调度。

4)运行队列

运行队列runqueue是内核支持调度的重要数据结构。每一个逻辑CPU有一个运行队列,减少CPU之间的竞争。

5)调度域

一般调度时只是从CPU自己的运行队列选择一个要运行的进程,必然会出现CPU资源利用不平衡,资源浪费。当一个CPU的运行队列比较短时,内核就要向其他CPU的运行队列查询,如果其他的CPU运行队列比自己的长,内核会把一些进程从其他CPU运行队列里摘除,然后加入到自己的运行队列,叫做CPU负载平衡。

SMP对称多处理机:每一个CPU有自己独立的cache、寄存器和运算单元。

cache设计原理是基于访问的局部性原理,包括时间局部性原理和空间局部性原理。时间局部性原理是指CPU刚访问过的数据,在不久的将来还会访问到;空间局部性原理是指CPU访问了某一数据后,很可能会访问这一数据空间上相邻的数据。cache存放的是内存映像,或者被CPU修改过的数据,以后刷新到内存。

在做CPU负载平衡时,主要的出发点时当进程被移动到另一个CPU的运行队列后,该进程的运行效率不能降低。主要因素就是cache,或者说访问数据的速度。根据访问cache和内存的速度,内核把逻辑CPU划分成组。首先是分成几个很小的组,然后几个小组构成大组,几个大组又可以构成更大的组,最后一个最大的组包含所有的CPU,这样一个逻辑CPU同时属于一个小组,中组,大组,最后一个最大的组,这种分组的设计思想称作调度域

具体实现上,一个运行队列有一个调度域链表,每个调度域包含几个调度组,调度组用双向链表连接在一起。

6)抢占

preempt_disable()禁止抢占

preempt_enable()打开抢占

spin_lock()会间接使用禁止函数,所以这样的函数执行时,禁止抢占。

kernel态抢占和用户态抢占

7)调度的时间复杂度 O(1)

3.5并发控制原理

1、阻塞操作 当系统需要访问某些资源时,可能需要阻塞进程等待资源状态,在这种情况下,某些资源可能需要加锁保护,以防止阻塞进程被唤醒以前,资源的状态被破坏。

2、中断 在内核里设置一些可抢占点,把一些关键的程序片段标志成不可抢占。在这些程序片段里可以安全的操作内核数据结构而不必担心被切换到其他进程。

1)原子操作

在很多体系结构下,原子操作需要锁总线和刷洗处理器的流水线,所以原子操作相对于普通的操作通常开销还是要更大一些。

2)自旋锁(转载)

互斥锁一样,一个执行单元要想访问被自旋锁保护的共享资源,必须先得到锁,在访问完共享资源后,必须释放锁。如果在获取自旋锁时,没有任何执行单元保持该锁,那么将立即得到锁;如果在获取自旋锁时锁已经有保持者,那么获取锁操作将自旋在那里,直到该自旋锁的保持者释放了锁。自旋锁是一种比较低级的保护数据结构或代码片段的原始方式,这种锁可能存在两个问题:
死锁。试图递归地获得自旋锁必然会引起死锁:递归程序的持有实例在第二个实例循环,以试图获得相同自旋锁时,不会释放此自旋锁。在递归程序中使用自旋锁应遵守下列策略:递归程序决不能在持有自旋锁时调用它自己,也决不能在递归调用时试图获得相同的自旋锁。此外如果一个进程已经将资源锁定,那么,即使其它申请这个资源的进程不停地疯狂“自旋”,也无法获得资源,从而进入死循环
过多占用cpu资源。如果不加限制,由于申请者一直在循环等待,因此自旋锁在锁定的时候,如果不成功,不会睡眠,会持续的尝试,cpu的时候自旋锁会让其它process动不了. 因此,一般自旋锁实现会有一个参数限定最多持续尝试次数. 超出后, 自旋锁放弃当前time slice. 等下一次机会。
自旋锁(spinlock)是一种轻量级的同步原语,它的获得或等待只需要很少的指令并不需要发生上下文切换。

3)信号量

生产者-消费者问题

变量的操作必须使用特殊的原子操作来完成,即P操作和V操作。P操作是对信号量进行减少,V操作是对信号量进行增加。

信号量的数值(即缓冲区中元素的个数)是不可能为负数的,所以当信号量为0时,P操作就会自动进入睡眠状态,直到随后的V操作将它唤醒为止。V操作在任何时候都不会进入到睡眠状态,原因是信号量的数值可以表示为足够大,相当于一个无界缓冲区。在LINUX中P、V操作对应为DOWN和UP操作。

6)尝试加锁(try_lock)

系统在试图操作关键数据结构的时候先测试是否能够获得保护这个数据结构的原语,如果不能,系统将转到其他执行路径。

3同步原语

原语是由若干条指令组成的,用于完成一定功能的一个过程。由若干个机器指令构成的完成某种特定功能的一段程序,具有不可分割性·即原语的执行必须是连续的,在执行过程中不允许被中断。

同步原语一般是用来保护数据和资源的,所以在设计同步原语的时候一般把它附在被保护的数据结构上,如果这个功能可以使用原子操作实现,应该优先使用原子操作;占用时间不长的一些数据结构操作,例如链表插入,可以用自旋锁或者读写锁来保护;占用时间较长的,或者需要阻塞等待外部资源的,可以使用信号量;还要注意对有些可能被中断处理程序修改的数据结构的操作需要关中断来保护(local_irq_disable()和local_irq_enable()),通过设置CPU寄存器来阻塞中断,当开中断时,被阻塞的中断会重新到达,不会丢失。

比较常用的原子操作原语有atomaic_read、atomaic_set、atomic_add、atomic_sub、atomic_add_and_test、atomic_sub_and_test等。内核利用它们来处理简单数据或者利用它们来构造更复杂的同步原语。

在内核获得自旋锁、读写锁、关中断时,内核抢占是被关掉的。

信号量

信号量的实现前提是原子操作。由于信号量需要维护一个计数器来记录信号量的数值以及一些其他需要保护的数据,所以必须使用原子操作来保护他们。在LINUX内核中,信号量关键数据的保护是借助自旋锁来完成的。

互斥锁(mutex)是一种新的数据结构,是一个信号量的特例,数值仅为0或1。

内核中提供互斥锁的原子操作:mutex_init()、mutex_lock()和mutex_unlock()。

mutex_init()是初始化struct mutex结构,count初始值为1,表示解锁状态。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值