我是如何学习写一个操做系统(七):进程的同步与信号量

前言

在多进程的运行环境下,进程是并发执行的,不一样进程间存在着不一样的相互制约关系。为了协调进程之间的相互制约关系,达到资源共享和进程协做,避免进程之间的冲突,引入了进程同步的概念。并发

临界资源

多个进程能够共享系统中的各类资源,但其中许多资源一次只能为一个进程所使用,咱们把一次只容许一个进程使用的资源成为临界资源。 对临界资源的访问,必须互斥的进行。每一个进程中,访问临界资源的那段代码成为临界区。函数

为了保证临界资源的正确使用,能够把临界资源的访问过程分为四个部分。spa

  • 进入区。为了进入临界区使用临界资源,在进入去要检查能否进入临界区。
  • 临界区。进程中访问临界资源的那段代码。
  • 退出区。将正在访问临界区的标志清除。
  • 剩余区。代码中的其他部分。

通常实现进程的同步有这几种方法:操作系统

  • 提过硬件提供的实现
  • 信号量
  • 管程

生产者-消费者实例

下面的代码就分别是生产者进程和消费者进程,而buffer就是临界资源线程

  • 当生产者要访问临界资源时,会先判断buffer是否是已经满了,而消费者则判断buffer是否是空的,这就是访问临界资源的进入区指针

  • 而中间对buffer的操做就是临界区code

  • 最后对counter的加减就是设置对临界区访问的标志队列

  • 可是这里依旧也有可能出现问题,好比当进程走到in = (in + 1) % BUFFER_SIZE;的时候,这时候操做系统进行调度,这时候的counter的值可能仍是0,因此消费者进程可能就会出现问题进程

这里的处理能够是利用关闭中断来限制进程的切换,可是在多核CPU下同样无论用资源

这里就涉及到了临界区的保护了

#define BUFFER_SIZE 10
typedef struct {  . . . } item;
item buffer[BUFFER_SIZE];
int in = out = counter = 0
复制代码
while (true) {
  while(counter== BUFFER_SIZE)
    ; 
  buffer[in] = item;
  in = (in + 1) % BUFFER_SIZE;
  counter++;
}  
复制代码
while (true) {
  while(counter== 0) 
    ;   
  item = buffer[out];
  out = (out + 1) % BUFFER_SIZE;
  counter--;
}  
复制代码

信号量

什么是信号量

为了防止出现因多个程序同时访问一个共享资源而引起的一系列问题,咱们须要一种方法,它能够经过生成并使用令牌来受权,在任一时刻只能有一个执行线程访问代码的临界区。

为了不像上面同样会发生竞争条件,程序对信号量访问都是原子操做,且只容许对它进行等待(即P(信号变量))和发送(即V(信号变量))信息操做。

信号量的工做原理

因为信号量只能进行两种操做等待和发送信号,即P(sv)和V(sv),他们的行为是这样的:

  • P(sv):若是sv的值大于零,就给它减1;若是它的值为零,就挂起该进程的执行
  • V(sv):若是有其余进程因等待sv而被挂起,就让它恢复运行,若是没有进程因等待sv而挂起,就给它加1.

举个例子,就是两个进程共享信号量sv,一旦其中一个进程执行了P(sv)操做,它将获得信号量,并能够进入临界区,使sv减1。而第二个进程将被阻止进入临界区,由于当它试图执行P(sv)时,sv为0,它会被挂起以等待第一个进程离开临界区域并执行V(sv)释放信号量,这时第二个进程就能够恢复执行。

Linux 0.11的进程同步

在Linux 0.11里是没有实现信号量的,考虑后面会本身实现一个。这里先看一下Linux 0.11里用来进行进程同步的两个函数

sleep_on

  • p实际上指的是一个等待队列

  • 若是当前进程是进程0或者无效,就直接退出

  • 而后把要等待的进程放到等待队列的头节点,把状态设置为不可中断的等待状态

    这里队列的造成很是很是的隐蔽,首先把用tmp指向以前的进程,在把当前要睡眠的进程放入,而之因此能造成队列,是由于如今放入队列的进程的tmp做为局部变量是保存在这个进程的堆栈中的,这样在把进程切换回来的时候,tmp就天然的指向上一个进程了。

  • 最后当这个进程被唤醒的时候,会回到if语句唤醒等待队列中全部进程

void sleep_on(struct task_struct **p) {
	struct task_struct *tmp;

	if (!p)
		return;
	if (current == &(init_task.task))
		panic("task[0] trying to sleep");
	tmp = *p;
	*p = current;
	current->state = TASK_UNINTERRUPTIBLE;
	schedule();
	if (tmp)
		tmp->state=0;
}
复制代码

wake_up

  • 唤醒 *p 指向的任务。 *p是任务等待队列头指针。因为新等待任务是插入在等待队列头指针处的,
void wake_up(struct task_struct **p) {
	if (p && *p) {
		(**p).state=0;          // 置为就绪(可运行)状态TASK_RUNNING.
		*p=NULL;
	}
}
复制代码

小结

首先居然有了多进程,那在访问共享资源的时候天然就会发生制约关系,因此才引入了进程同步的概念。

而进程同步的关键就是对临界区的保护,信号量就是一种能够很好的实现对临界区保护的方法

相关文章
相关标签/搜索