免费注册 查看新帖 |

Chinaunix

  平台 论坛 博客 文库
最近访问板块 发新帖
查看: 2285 | 回复: 1
打印 上一主题 下一主题

详谈Linux 2_4_x内核信号量机制 [复制链接]

论坛徽章:
0
跳转到指定楼层
1 [收藏(0)] [报告]
发表于 2003-05-28 14:57 |只看该作者 |倒序浏览
详谈Linux 2_4_x内核信号量机制
                      jerryhj@yeah.net

    信号量作为一种同步机制,在每个成熟的现代操作系统的实现过程中,起着不可替代的作用,对于Linux也不例外,在Linux 2_4_x下,实现内核信号量机制的代码虽然不长,但由于涉及到多个进程间的相互干扰,并且在Linux发展过程中,不断进行优化,所以非常难于理解,在讲解Linux源代码的各类文章中,也大都对此语焉不详,本人通过认真阅读,对这部分代码有了一定的了解,这里介绍出来,希望对大家有所帮助。
   
    首先看看信号量的概念:
    1.信号量的类型定义:
    每个信号量至少须记录两个信息:信号量的值和等待该信号量的进程队列。它的类型定义如下:(用类PASCAL语言表述)
    semaphore = record
         value: integer;
         queue: ^PCB;
    end;
其中PCB是进程控制块,是操作系统为每个进程建立的数据结构。
s.value>;=0时,s.queue为空;
s.value<0时,s.value的绝对值为s.queue中等待进程的个数;

    2.PV原语:
    对一个信号量变量可以进行两种原语操作:p操作和v操作,定义如下:
     procedure p(var s:samephore);
     {
       s.value=s.value-1;
       if (s.value<0) asleep(s.queue);
     }
     procedure v(var s:samephore);
     {
       s.value=s.value+1;
       if (s.value<=0) wakeup(s.queue);
     }
其中用到两个标准过程:
    asleep(s.queue);执行此操作的进程的PCB进入s.queue尾部,进程变成等待状态
    wakeup(s.queue);将s.queue头进程唤醒插入就绪队列。s.value初值为1时,可以用来实现进程的互斥。p操作和v操作是不可中断的程序段,称为原语。如果将信号量看作共享变量,则pv操作为其临界区,多个进程不能同时执行,一般用硬件方法保证。一个信号量只能置一次初值,以后只能对之进行p操作或v操作。

    再来看看在对应的Linux2.4.x具体实现中,信号量的数据结构:
    struct semaphore {
            atomic_t count;
            int sleepers;
            wait_queue_head_t wait;
    };
可以看到相对于操作系统原理中的定义,数据结构多了一个sleepers成员,这个变量是为使主要分支代码执行速度更快,所作的优化。
    其中,count相当于资源计数,为正数或0时表示可用资源数,-1则表示没有空闲资源且有等待进程,而其他的负数仅仅一个中间过程,当所有进程都稳定下来后,将最终变为-1。
    sleepers相当于一个状态标志,它仅有0和1两个值,有时会出现2,但也只是中间状态,并没有实际意义。当sleepers为0时,表示没有进程在等待,或等待中的这些进程正在被唤醒过程中。当sleepers为1时,表示至少有一个进程在等待中
    wait是等待队列。
    在这里可以看到,等待进程的数量并不被关心。

    下面我们来看函数,Linux2.4.x具体实现中,down()函数相当于p操作,up()函数相当于v操作。
    在down()函数中,count做原子减1操作,如果结果不小于0[第4行],则表示成功申请,从down()中返回,如果结果为负(大多数情况是-1,注意判断“结果不小于0”的原因,是因为结果有可能是其他负数),表示需要等待,则调用__down_fail()[第7行],
(include/asm-i386/semaphore.h):
static inline void down(struct semaphore * sem)
{
1        __asm__ __volatile__(
2                "# atomic down operation\n\t"
3                LOCK "decl %0\n\t"     /* --sem->;count */
4                "js 2f\n"
5                "1:\n"
6                ".section .text.lock,\"ax\"\n"
7                "2:\tcall __down_failed\n\t"
8                "jmp 1b\n"
9                ".previous"
10                :"=m" (sem->;count)
11                :"c" (sem)
12                :"memory");
}

__down_fail()调用__down(),
(arch/i386/kernel/semaphore.c):
asm(
".text\n"
".align 4\n"
".globl __down_failed\n"
"__down_failed:\n\t"
13        "pushl %eax\n\t"
14        "pushl %edx\n\t"
15        "pushl %ecx\n\t"
16        "call __down\n\t"
17        "popl %ecx\n\t"
18        "popl %edx\n\t"
19        "popl %eax\n\t"
20        "ret"
);

__down()用C代码实现。
(arch/i386/kernel/semaphore.c):
void __down(struct semaphore * sem)
{
21        struct task_struct *tsk = current;
22        DECLARE_WAITQUEUE(wait, tsk);
23        tsk->;state = TASK_UNINTERRUPTIBLE;
24        add_wait_queue_exclusive(&sem->;wait, &wait);
25
26        spin_lock_irq(&semaphore_lock);
27        sem->;sleepers++;
28        for (;;) {
29                int sleepers = sem->;sleepers;
30
31                /*
32                 * Add "everybody else" into it. They aren't
33                 * playing, because we own the spinlock.
34                 */
35                if (!atomic_add_negative(sleepers - 1, &sem->;count)) {
36                        sem->;sleepers = 0;
37                        break;
38                }
39                sem->;sleepers = 1;      /* us - see -1 above */
40                spin_unlock_irq(&semaphore_lock);
41
42                schedule();
43                tsk->;state = TASK_UNINTERRUPTIBLE;
44                spin_lock_irq(&semaphore_lock);
45        }
46        spin_unlock_irq(&semaphore_lock);
47        remove_wait_queue(&sem->;wait, &wait);
48        tsk->;state = TASK_RUNNING;
49        wake_up(&sem->;wait);
}
    代码中关于声明一个等待队列,将本进程加入等待队列,然后睡眠等等这些操作,是在Linux内核中,进行主动进程调度的标准方法,这里就不加以特殊说明了。
    但需要提醒大家注意的是,调用schedule()函数的位置[第42行]是在加锁范围之外,也就是spin_unlock_irq(&semaphore_lock)[第40行]之后,spin_lock_irq(&semaphore_lock)[第44行]之前。这么做的原因是spin_lock_irq操作如果加锁失败,那么它会不停重试,直到成功为止,如果上一次加锁成功的那个操作占用锁时间过长,比如在加锁范围内调用了schedule()函数(schedule函数可能会导致当前进程陷入睡眠),就会导致大量CPU时间的浪费。
    同样是由于调用了schedule()函数,就意味着这种信号量机制,只能用在进程与进程之间的互斥中。如果你在中断处理函数中,或softirq操作中使用down()函数,schedule()函数包含的如下语句:
(kernel/sched.c):
50           if (in_interrupt()) {
51                  printk("Scheduling in interrupt\n");
52                  BUG();
53            }
就会产生错误。

    如上所诉,下面的场景都是建立在两个或多个进程间的,还需要说明的一点是,这些场景都是在SMP,也就是在多CPU情况下的。
    为什么要强调这点呢,因为当进程运行在内核态时,是不会发生强制性的进程切换的,就是说在代码中没有明确调用schedule()函数,就不会发生进程切换,如果进程在内核态发现需要进行进程切换,而自己又不想进入睡眠状态,一般是设置进程数据结构中的标志need_resched,表示本进程在返回用户态前夕,需要进行一次进程切换[第55行]:
(arch/i386/kernel/entry.S):
    ENTRY(ret_from_sys_call)
54          cli                                # need_resched and signals atomic test
55          cmpl $0,need_resched(%ebx)
56          jne reschedule
57          cmpl $0,sigpending(%ebx)
58          jne signal_return

    还有一个问题,如果代码在内核态运行过程中发生中断,会不会发生进程切换呢?的确,当代码在用户态运行时发生中断,是有可能发生进程切换的,这也是为什么当我们在用户态编写一个死循环程序时,不会把CPU时间耗尽,因为有时钟中断,当该进程的时间片到了时,它不想让也得让了。那么在内核态会不会发生这种情况吗?不会,因为在从中断返回时,会首先判断是在什么运行态下发生中断的,只有在用户态发生的中断,才有可能在这里进行进程切换,换句话,如果你在驱动程序中编了个死循环,那这个CPU恐怕就真的死循环了[第63行]:
(arch/i386/kernel/entry.S):
    ENTRY(ret_from_intr)
59          GET_CURRENT(%ebx)
60  ret_from_exception:
61          movl EFLAGS(%esp),%eax                # mix EFLAGS and CS
62          movb CS(%esp),%al
63          testl $(VM_MASK | 3),%eax        # return to VM86 mode or non-supervisor?
64          jne ret_from_sys_call
65          jmp restore_all

    回到我们原来的话题,在下面场景描述中,不断会用到"进程B从这一点开始运行","进程C从那一点继续"等等说明,这在单CPU情况下显然是不可能的,在说明过程中就不具备典型意义。所以我们的场景是建立在多CPU情况下的。
   
    我们继续来看代码,除去有关等待队列操作和进程切换外,down函数代码中仅剩下与sleepers变量有关的部分,和最后一句wake_up(&sem->;wait)[第49行]。请注意,函数开始部分,加入等待队列时调用的是add_wait_queue_exclusive()函数[第24行],这意味着这里的wake_up(也包括下面将介绍的up函数中的wake_up)仅会唤醒在队列头的那个等待进程。说到这里需要提一句,在Linux 2.2的代码中,wake_up函数是会唤醒所有的等待进程的,但这样会导致CPU资源的浪费,比如说,在这个信号量上有100个进程正在睡眠,当信号量被up时,只可能有一个进程被唤醒,但实际上另外99进程也都被唤醒了,只不过他们发现自己晚到了一步,只好又去睡眠,2.4上用仅唤醒在队列头的那个等待进程的方法,来修正了这个问题,但又引入了另一个问题:睡眠队列是先入先出的,与进程优先级没有关系,这就有可能使进程优先级高的进程不会被优先唤醒。这个问题在Linux 2.2中显然是没有的,有关这个功能的代码不知是否会在以后的版本中被引入。
   
    现在来看与sleepers变量有关的部分,关于sleepers的所有操作,都在down函数之内,而且还都是在加锁范围以内的,也就是说对sleepers变量的操作是原子的,不会发生这个进程对sleepers变量的操作运行到一半,就被另一个进程所打断的现象。sleepers的初始值是0,而退出加锁范围前,sleepers不是被设置为0[第36行],就是被设置为1[第39行]。这就可以认为sleepers变量实际上仅有0和1两个值
        
    下面我们来构造一些场景进行说明:
场景一:
    count值为1,有一个进程企图获得该信号量。
    程序会在第3行对count进行减1操作,第4行判断发现count值大于等于0,所以在第5行直接返回。这个过程最简单。

场景二:
    count值为0,有一个进程企图获得该信号量。
    程序会在第3行对count进行减1操作,结果为-1,第4行判断发现count值小于0,所以跳至第7行执行__down_failed,进而在第16行执行__down函数。在__down函数中,首先声明睡眠队列,然后加锁,这时sleepers的值为初始值0,表示在这之前没有进程睡眠在这个信号量上,通过对这个值加1[第27行],然后又减1[第35行]的操作后,将这个值加回到count中[第35行],结果count当然仍为-1,所以跳至第39行将sleepers的值设为1,表示有进程在睡眠队列,sleepers这个值本身也是为下一次将它加回到count中做好准备。最后解锁,睡眠,等待被唤醒。
   
场景三:
    count值为-1,有一个进程企图获得该信号量。
    程序会在第3行对count进行减1操作,结果为-2,第4行判断发现count值小于0,所以跳至第7行执行__down_failed,进而在第16行执行__down函数。在__down函数中,首先声明睡眠队列,然后加锁,这时sleepers的值肯定为1(因为count值为-1,表示有进程睡眠在这个信号量上),通过对这个值加1[第27行],然后又减1[第35行]的操作后,将这个值加回到count中[第35行],结果为-1,这里等于将上个进程欠着count的,在sleepers中的那个值还给了count,但这个进程还得欠着,所以跳至第39行将sleepers的值设为1,表示有进程在睡眠队列,并为下一次将这个值加回到count中做好准备。最后解锁,睡眠,等待被唤醒。

场景四:
    count值为1,有两个进程同时企图获得该信号量。
    当进程A在第3行对count进行减1操作后,进程B又执行到第3行,并也对count进行减1操作,使其值为-1,但进程A在第4行仍旧会判断发现count值大于等于0,所以在第5行直接返回。这是因为第5行只是根据当前CPU的标志寄存器进行判断,而进程B所在的CPU执行的结果,并不会影响到进程A所在的CPU的标志寄存器。进程B以后的执行过程就相当于场景二。

场景五:
    count值为0,有两个进程同时企图获得该信号量。
    当进程A在第3行对count进行减1操作后,进程B又执行到第3行,并也对count进行减1操作,使其值为-2,进程A在第4行判断发现count值小于0,所以跳至第7行执行__down_failed,进而在第16行执行__down函数。在__down函数中,首先声明睡眠队列,然后加锁。这时进程B再运行,将被阻止在加锁语句上[第26行]。进程A发现sleepers的值为初始值0,表示在这之前没有进程睡眠在这个信号量上,通过对这个值加1[第27行],然后又减1[第35行]的操作后,将这个值加回到count中[第35行],结果仍为-2,所以跳至第39行将sleepers的值设为1,表示有进程在睡眠队列,并为下一次将这个值加回到count中做好准备。最后解锁,睡眠,等待被唤醒。由于进程A解锁,所以进程B得以继续运行,进程B发现sleepers的值为1,表示有进程睡眠在这个信号量上,通过对这个值加1[第27行],然后又减1[第35行]的操作后,将这个值加回到count中[第35行],结果为-1,仍不为0,所以跳至第39行将sleepers的值设为1,表示有进程在睡眠队列,并为下一次将这个值加回到count中做好准备。最后解锁,睡眠,等待被唤醒。

场景六:
    count值为-1,有两个进程同时企图获得该信号量。
    这个场景分析过程同场景五,结果依然是count值为-1,sleepers为1,这两个进程进入睡眠状态,等待被唤醒。
   
    对于count值其它负值的情况,其实是上述的某个场景未进行到稳定状态的结果,分析过程可以归并到上述的某个场景中。通过以上场景分析,我们可以总结出,稳定状态的结果实际上只有两种情况:
    1.count大于等于0,sleepers为0,没有进程睡眠在等待队列中。
    2.count为-1,sleepers为1,有进程睡眠在等待队列中。

    下面我们将up函数的执行,也插入到场景中来。
    首先分析一下up()函数,它的实现就简单了,up()利用汇编原子地将count加1,如果小于等于0表示有等待进程(大多数情况结果是0或正数,判断“小于等于0”而不是“等于0”的原因,是因为结果有可能是负数),则调用__up_wakeup(),进而调用__up()唤醒等待进程,否则直接返回。
(include/asm-i386/semaphore.h):
static inline void up(struct semaphore * sem)
{
66        __asm__ __volatile__(
67                "# atomic up operation\n\t"
68                LOCK "incl %0\n\t"     /* ++sem->;count */
69                "jle 2f\n"
70                "1:\n"
71                ".section .text.lock,\"ax\"\n"
72                "2:\tcall __up_wakeup\n\t"
73                "jmp 1b\n"
74                ".previous"
75                :"=m" (sem->;count)
76                :"c" (sem)
77                :"memory");
}

(arch/i386/kernel/semaphore.c):
asm(
".text\n"
".align 4\n"
".globl __up_wakeup\n"
"__up_wakeup:\n\t"
78        "pushl %eax\n\t"
79        "pushl %edx\n\t"
80        "pushl %ecx\n\t"
81        "call __up\n\t"
82        "popl %ecx\n\t"
83        "popl %edx\n\t"
84        "popl %eax\n\t"
85        "ret"
);

(arch/i386/kernel/semaphore.c):
void __up(struct semaphore *sem)
{
86        wake_up(&sem->;wait);
}

场景七:
    count值为0,有一个进程释放了该信号量。
    程序会在第68行对count进行加1操作,第69行判断发现count值大于0,所以在第70行直接返回。

场景八:
    count值为-1,有一个进程释放了该信号量,有一个进程在等待队列中睡眠。
    进程A会在第68行对count进行加1操作,结果为0,第69行判断发现count值小于等于0,所以跳至第72行执行__up_wakeup,进而在第81行执行__up函数,最后在第86行执行wake_up函数,唤醒在等待队列队列头的进程B。进程B被唤醒后,将继续执行第43,44行,继而执行到第35行,在这里,sleepers的值为1,经过减1[第35行]操作后,将这个值加回到count中[第35行],结果count仍为0,所以执行第36行,将sleepers的值设为0,表示状态为唤醒进程的过程中,然后跳出循环,做一些解琐和清除队列的操作后,调用wake_up函数[第49行],由于这个场景中只有一个进程在等待队列中睡眠,所以这里的wake_up函数将什么也不做。这时的状态为count为0,sleepers为0,没有其他进程在等待队列中睡眠。进程B最终会调用一个up函数,而进入场景七。

场景九:
    count值为-1,有一个进程释放了该信号量,有两个进程在等待队列中睡眠。
    这个场景的前一部分,和场景八相同,在进程B调用wake_up函数[第49行]后,在等待队列中睡眠的进程C被唤醒,进程C将继续执行第43,44行,继而执行到第35行,在这里,count值为0,sleepers的值也为0,经过减1[第35行]操作后,将这个值加回到count中[第35行],结果为-1。所以跳至第39行将sleepers的值设为1,表示有进程在睡眠队列,并为下一次将这个值加回到count中做好准备。最后解锁,而又进入睡眠,等待再次被唤醒。被唤醒的进程B最终会调用一个up函数,来唤醒又进入睡眠状态的进程C,而进入场景八。
    这里我们可以看到一个up操作实际上连续的唤醒了两个进程,只不过进程C判断发现"好像其实并没人打算叫醒我,所以我还是接着睡吧",但进程C被唤醒不是没有意义的,因为count,以及sleepers这两个标志值,是由它来恢复为睡眠状态的值的,即count为-1,sleepers为1。
   
我们来看一个复杂的场景。
场景十:
    count值为-1,有两个进程同时释放了该信号量,有两个进程在等待队列中睡眠。
    进程A会在第68行对count进行加1操作,结果为0,第69行判断发现count值小于等于0,所以跳至第72行执行__up_wakeup,进而在第81行执行__up函数,最后在第86行执行wake_up函数,唤醒在等待队列队列头的那个进程。这时进程B执行,它也会在第68行对count进行加1操作,此时结果为1,第69行判断发现count值大于0,所以在第70行直接返回。注意,进程B没有执行wake_up函数,现在我们来看进程A是如何将两个进程都唤醒的,首先等待队列中队列头的进程C被唤醒后,将继续执行第43,44行,继而执行到第35行,在这里,sleepers的值为1,经过减1[第35行]操作后,将这个值加回到count中[第35行],结果仍为1,atomic_add_negative这个函数是判断结果是否为负数,所以这里会执行第36行,将sleepers的值设为0,表示状态为唤醒进程的过程中,然后跳出循环,做一些解琐和清除队列的操作后,调用wake_up函数[第49行]唤醒进程D,进程D被唤醒后,将继续执行第43,44行,继而执行到第35行,在这里,sleepers的值为0,经过减1[第35行]操作后,将这个值加回到count中[第35行],结果为0,所以也会执行第36行,将sleepers的值设为0,表示状态为唤醒进程的过程中,然后跳出循环,做一些解琐和清除队列的操作后,调用wake_up函数[第49行],由于现在已经没有进程在等待队列中睡眠,所以这里的wake_up函数将什么也不做。
    这个场景可以看到第49行的wake_up函数,也会实际的承担唤醒进程的工作。当然如果这里还有一个进程E在等待队列中睡眠,那它就会像场景九中的进程C一样,被唤醒,然后又去睡眠。

下面看一个,down函数运行未完成,而up函数就开始运行的场景。
场景十一:
    count值为0,有一个进程企图获得该信号量,同时一个进程释放了该信号量。
    当进程A在第3行对count进行减1操作后,进程B在第68行对count进行加1操作,使其值恢复为0,然后在第69行判断发现count值小于等于0,所以跳至第72行执行__up_wakeup,进而在第81行执行__up函数,最后在第86行执行wake_up函数,而此时实际上并没有进程在等待队列中睡眠,所以这句wake_up[第86行]将什么也不做。这时进程A开始执行,因为它还不知道进程B已经执行了一次up操作,所以它在第4行判断发现count值小于0,然后跳至第7行执行__down_failed,进而在第16行执行__down函数。在__down函数中,首先声明睡眠队列,然后加锁。进程A发现sleepers的值为初始值0,表示在这之前没有进程睡眠在这个信号量上,通过对这个值加1[第27行],然后又减1[第35行]的操作后,将这个值加回到count中[第35行],由于之前的up操作,所以结果为0,然后当然是执行第36行,将sleepers的值设为0,表示状态为唤醒进程的过程中,然后跳出循环,做一些解琐和清除队列的操作后,调用wake_up函数[第49行],由于已经没有进程在等待队列中睡眠,所以这里的wake_up函数将什么也不做。
    请注意,up操作过程中是没有锁机制的,就是说,在down操作运行的任何阶段,即使是在加锁范围以内,up都是有可能发生的。如果它在add_wait_queue_exclusive[第24行]以后,atomic_add_negative(sleepers - 1, &sem->;count)[第35行]以前发生,那么up操作的wake_up函数[第86行]就会把刚刚加入到睡眠队列,但还没有真正睡眠的这个进程又加回到运行队列。然后进程A在第36行退出循环。另外atomic_add_negative(sleepers - 1, &sem->;count)[第35行]是一个原子操作,add_wait_queue_exclusive[第24行]操作过程中有专门的队列锁,所以在这两句运行中,是不会被打断的。
    这里我们看到了down函数进入循环,而又发现"其实不需要睡眠"的例子。

最后,我们看看count值为其它负数时,即不稳定状态下,up函数被执行的情况。
场景十二:
    count值为0,有两个进程同时企图获得该信号量,同时一个进程释放了该信号量。
    当进程A在第3行对count进行减1操作后,进程B又执行到第3行,并也对count进行减1操作,使其值为-2,此时进程C运行,并在第68行对count进行加1操作,使其值变为-1,然后第69行判断发现count值小于等于0,所以跳至第72行执行__up_wakeup,进而在第81行执行__up函数,最后在第86行执行wake_up函数,而此时实际上并没有进程在等待队列中睡眠,所以这句wake_up将什么也不做。然后进程A在第4行判断发现count值小于0,所以跳至第7行执行__down_failed,进而在第16行执行__down函数。在__down函数中,首先声明睡眠队列,然后加锁。这时进程B再运行,将被阻止在加锁语句上[第26行]。进程A发现sleepers的值为初始值0,表示在这之前没有进程睡眠在这个信号量上,通过对这个值加1[第27行],然后又减1[第35行]的操作后,将这个值加回到count中[第35行],结果为-1,所以跳至第39行将sleepers的值设为1,表示有进程在睡眠队列,并为下一次将这个值加回到count中做好准备。最后解锁,睡眠,等待被唤醒。由于进程A解锁,所以进程B得以继续运行,进程B发现sleepers的值为1,表示有进程睡眠在这个信号量上,通过对这个值加1[第27行],然后又减一[第35行]的操作后,将这个值加回到count中[第35行],结果为0,所以执行第36行,将sleepers的值设为0,表示状态为唤醒进程的过程中,然后跳出循环,做一些解琐和清除队列的操作后,调用wake_up函数[第49行],唤醒在等待队列中睡眠的进程A,进程A将继续执行第43,44行,继而执行到第35行,在这里,count值为0,sleepers的值也为0,经过减1[第35行]操作后,将这个值加回到count中[第35行],结果为-1。所以跳至第39行将sleepers的值设为1,表示有进程在睡眠队列,并为下一次将这个值加回到count中做好准备。最后解锁,而又进入睡眠,等待再次被唤醒。被唤醒的进程B最终会调用一个up函数,来唤醒又进入睡眠状态的进程A,而进入场景八。
    这个场景中,我们发现一个有意思的事情,就是进程A先于进程B进入加锁范围,但反而是进程B首先被唤醒,但由于进程A和进程B分别运行于不同的CPU,且企图获得该信号量的时间基本相同,所以出现这种情况也没什么大的关系。
   
    也许有人会问,在实际情况中,上面说的这些场景真的有可能发生吗?考虑到当某个进程运行到上述的一个特定点,此时在这个CPU上发生中断,该CPU转而去处理这个中断(当然它处理完中断还是会从这个特定点继续向下运行的,另外这个特定点也不会在加锁范围之内,因为spin_lock_irq函数会执行一句cli来关掉中断),这时另一个CPU的进程没有中断打断,而继续运行,上面描述的场景就发生了。虽然这种几率是非常小的,但操作系统的实现过程中,却不得不考虑它们。
   
    通过上面十二个场景,我们可以看到有关内核信号量的代码,虽然不长,但需要考虑的情况却要如此的复杂而全面。我希望通过这篇文章,可以对正在阅读Linux源代码的朋友们,多少提供一些帮助。

论坛徽章:
1
白银圣斗士
日期:2015-11-30 23:56:19
2 [报告]
发表于 2015-12-31 12:47 |只看该作者
mark。。。。。

您需要登录后才可以回帖 登录 | 注册

本版积分规则 发表回复

  

北京盛拓优讯信息技术有限公司. 版权所有 京ICP备16024965号-6 北京市公安局海淀分局网监中心备案编号:11010802020122 niuxiaotong@pcpop.com 17352615567
未成年举报专区
中国互联网协会会员  联系我们:huangweiwei@itpub.net
感谢所有关心和支持过ChinaUnix的朋友们 转载本站内容请注明原作者名及出处

清除 Cookies - ChinaUnix - Archiver - WAP - TOP