Linux內核同步機制之信號量與鎖


  Linux內核同步控制方法有很多,信號量、鎖、原子量、RCU等等,不同的實現方法應用於不同的環境來提高操作系統效率。首先,看看我們最熟悉的兩種機制——信號量、鎖。

 

一、信號量

 

       首先還是看看內核中是怎么實現的,內核中用struct semaphore數據結構表示信號量(<linux/semphone.h>中):

 

View Code
1 struct semaphore {  
2 spinlock_t lock;
3 unsigned int count;
4 struct list_head wait_list;
5 };

 

   其中lock為自旋鎖,放到這里是為了保護count的原子增減,無符號數count為我們競爭的信號量(PV操作的核心),wait_list為等待此信號量的進程鏈表。

 

初始化:

 

       對於這一類工具類使用較多的機制,包括用於同步互斥的信號量、鎖、completion,用於進程等待的等待隊列、用於Per-CPU的變量等等,內核都提供了兩種初始化方法,靜態與動態方式。

 

1)      靜態初始化,實現代碼如下:

 

View Code
1 #define __SEMAPHORE_INITIALIZER(name, n)                \  
2 { \
3 .lock = __SPIN_LOCK_UNLOCKED((name).lock), \
4 .count = n, \
5 .wait_list = LIST_HEAD_INIT((name).wait_list), \
6 }
7
8 #define DECLARE_MUTEX(name) \
9 struct semaphore name = __SEMAPHORE_INITIALIZER(name, 1)

 

      可以看到,這種初始化使我們在編程的時候直接用一條語句DECLARE_MUTEX(name);就可以完成申明與初始化,另一種下面要說的動態初始化方式申請與初始化分離。

 

2)      我們看到,靜態初始化時信號量的count值初始化為1,當我們需要初始化為0時需要用動態初始化方法。

 

View Code
1 #define init_MUTEX(sem)     sema_init(sem, 1)  
2 #define init_MUTEX_LOCKED(sem) sema_init(sem, 0)
3
4 static inline void sema_init(struct semaphore *sem, int val)
5 {
6 static struct lock_class_key __key;
7 *sem = (struct semaphore) __SEMAPHORE_INITIALIZER(*sem, val);
8 lockdep_init_map(&sem->lock.dep_map, "semaphore->lock", &__key, 0);
9 }

 

操作:

 

獲取信號量

 

View Code
 1 /*獲取信號量*/  
2 void down(struct semaphore *sem)
3 {
4 unsigned long flags;
5
6 spin_lock_irqsave(&sem->lock, flags);
7 if (likely(sem->count > 0))
8 sem->count--;
9 else
10 __down(sem);
11 spin_unlock_irqrestore(&sem->lock, flags);
12 }

__down(sem)最終由下面函數實現

 

View Code
 1 static inline int __sched __down_common(struct semaphore *sem, long state,  
2 long timeout)
3 {
4 struct task_struct *task = current;
5 struct semaphore_waiter waiter;
6
7 list_add_tail(&waiter.list, &sem->wait_list);
8 waiter.task = task;
9 waiter.up = 0;
10
11 for (;;) {
12 if (signal_pending_state(state, task))
13 goto interrupted;
14 if (timeout <= 0)
15 goto timed_out;
16 __set_task_state(task, state);
17 spin_unlock_irq(&sem->lock);
18 timeout = schedule_timeout(timeout);
19 spin_lock_irq(&sem->lock);
20 if (waiter.up)
21 return 0;
22 }
23
24 timed_out:
25 list_del(&waiter.list);
26 return -ETIME;
27
28 interrupted:
29 list_del(&waiter.list);
30 return -EINTR;
31 }

釋放信號量

 

View Code
 1 void up(struct semaphore *sem)  
2 {
3 unsigned long flags;
4
5 spin_lock_irqsave(&sem->lock, flags);
6 if (likely(list_empty(&sem->wait_list)))
7 sem->count++;
8 else
9 __up(sem);
10 spin_unlock_irqrestore(&sem->lock, flags);
11 }
 
View Code
1 static noinline void __sched __up(struct semaphore *sem)  
2 {
3 struct semaphore_waiter *waiter = list_first_entry(&sem->wait_list,
4 struct semaphore_waiter, list);
5 list_del(&waiter->list);
6 waiter->up = 1;
7 wake_up_process(waiter->task);
8 }

      從上面代碼可以看出,信號量的獲取和釋放很簡單,不外乎修改count值、加入或移除等待隊列元素,其中count值的修改需要自旋鎖的支持。還有幾個down和up類函數,實現類似,使用時可以看看源碼不同之處。

 

運用:

       用信號量我們實現兩個線程的同步,我們用kernel_thread創建兩個線程,對變量num的值進行同步訪問,代碼如下,文件為semaphore.c

View Code
 1 #include <linux/init.h>  
2 #include <linux/kernel.h>
3 #include <linux/module.h>
4
5 #include <linux/sched.h>
6 #include <linux/semaphore.h>
7 #define N 15
8
9 MODULE_LICENSE("GPL");
10
11 static unsigned count=0,num=0;
12 struct semaphore sem_2;
13 DECLARE_MUTEX(sem_1);/*init 1*/
14
15
16 int ThreadFunc1(void *context)
17 {
18 char *tmp=(char*)context;
19 while(num<N){
20 down(&sem_1);
21 printk("<2>" "%s\tcount:%d\n",tmp,count++);
22 num++;
23 up(&sem_2);
24 }
25 return 0;
26 }
27 int ThreadFunc2(void *context)
28 {
29 char *tmp=(char*)context;
30 while(num<N){
31 down(&sem_2);
32 printk("<2>" "%s\tcount:%d\n",tmp,count--);
33 num++;
34 up(&sem_1);
35 }
36 return 0;
37 }
38
39 static __init int semaphore_init(void)
40 {
41 char *ch1="this is first thread!";
42 char *ch2="this is second thread!";
43 init_MUTEX_LOCKED(&sem_2);/*init 0*/
44
45 kernel_thread(ThreadFunc1,ch1,CLONE_KERNEL);
46 kernel_thread(ThreadFunc2,ch2,CLONE_KERNEL);
47
48 return 0;
49 }
50
51 static void semaphore_exit(void)
52 {
53 }
54
55 module_init(semaphore_init);
56 module_exit(semaphore_exit);
57
58 MODULE_AUTHOR("Mike Feng");

 

實現結果如下。

可以看到線程1和線程2交替運行,實現了同步。

 

讀、寫信號量:

 

       類似操作系統中學習的讀者、寫者問題,內核中,許多任務可以划分為兩種不同的工作類型:一些任務只需要讀取受保護的數據結構,而其他的則必須做出修改。循序多個並發的讀者是可能的,只要他們之中沒有哪個要做出修改。Linux內核為這種情形提供了一種特殊的信號量類型——讀、寫信號量。struct rw_semaphore作為其數據結構,初始化和信號量類似,down_read、up_read等類函數實現信號量控制,這些函數實現比較復雜,用到了讀寫鎖(將在后面分析),有興趣可以看看,。我們運用讀、寫信號實現哪個古老的讀者、寫者同步問題:

文件down_read.c

 

View Code
 1 #include <linux/init.h>  
2 #include <linux/module.h>
3 #include <linux/kernel.h>
4
5 #include <linux/sched.h>
6 #include <linux/rwsem.h>
7 #include <linux/semaphore.h>
8
9 MODULE_LICENSE("GPL");
10
11 static int count=0,num=0,readcount=0,writer=0;
12 struct rw_semaphore rw_write;
13 struct rw_semaphore rw_read;
14 struct semaphore sm_1;
15
16
17 int ThreadRead(void *context)
18 {
19 down_read(&rw_write);
20 down(&sm_1);
21 count++;
22 readcount++;
23 up(&sm_1);
24
25 printk("<2>" "Read Thread %d\tcount:%d\n",readcount,count);
26 msleep(10);
27 printk("<2>" "Read Thread Over!\n",readcount);
28
29 up_read(&rw_write);
30
31 return 0;
32 }
33 int ThreadWrite(void *context)
34 {
35 down_write(&rw_write);
36 writer++;
37
38 printk("<2>" "Write Thread %d\tcount:%d\n",writer,--count);
39 msleep(10);
40 printk("<2>" "Write Thread %d Over!\n",writer);
41
42 up_write(&rw_write);
43
44 return count;
45 }
46
47 static __init int rwsem_init(void)
48 {
49 static int i,iread=0,iwrite=0;
50 init_rwsem(&rw_read);
51 init_rwsem(&rw_write);
52 init_MUTEX(&sm_1);
53
54 for(i=0;i<2;i++){
55 kernel_thread(ThreadWrite,&i,CLONE_KERNEL);
56 iwrite++;
57 }
58
59 for(i=0;i<2;i++){
60 kernel_thread(ThreadRead,&i,CLONE_KERNEL);
61 iread++;
62 }
63 for(i=2;i<5;i++){
64 kernel_thread(ThreadRead,&i,CLONE_KERNEL);
65 iread++;
66 }
67 for(i=2;i<5;i++){
68 kernel_thread(ThreadWrite,&i,CLONE_KERNEL);
69 iwrite++;
70 }
71
72 return 0;
73 }
74
75 static void rwsem_exit(void)
76 {
77 }
78
79 module_init(rwsem_init);
80 module_exit(rwsem_exit);
81
82 MODULE_AUTHOR("Mike Feng");

實驗結果:

 從代碼上看,實現起來很簡單。

 

二、自旋鎖

 

讀寫信號量基於自旋鎖實現。內核中為如下結構:

 

View Code
 1 typedef struct {  
2 raw_spinlock_t raw_lock;
3 #ifdef CONFIG_GENERIC_LOCKBREAK
4 unsigned int break_lock;
5 #endif
6 #ifdef CONFIG_DEBUG_SPINLOCK
7 unsigned int magic, owner_cpu;
8 void *owner;
9 #endif
10 #ifdef CONFIG_DEBUG_LOCK_ALLOC
11 struct lockdep_map dep_map;
12 #endif
13 } spinlock_t;

其中raw_lock為實現的原子量控制。下面我們就信號量和自旋鎖實現我們上面用讀寫信號量實現的讀者、寫者問題:spinlock.c文件

 

View Code
 1 #include <linux/init.h>  
2 #include <linux/module.h>
3 #include <linux/kernel.h>
4
5 #include <linux/sched.h>
6 #include <linux/semaphore.h>
7 #include <linux/spinlock.h>
8
9 MODULE_LICENSE("GPL");
10
11 static int count=0,num=0,readcount=0,writer=0,writecount=0;
12 struct semaphore sem_w,sem_r;
13
14 spinlock_t lock1=SPIN_LOCK_UNLOCKED;
15
16 int ThreadRead(void *context)
17 {
18 down(&sem_r);
19 spin_lock(&lock1);
20 readcount++;
21 if(readcount==1)
22 down(&sem_w);
23 spin_unlock(&lock1);
24 up(&sem_r);
25
26 printk("<2>" "Reader %d is reading!\n",readcount);
27 msleep(10);
28 printk("<2>" "Reader is over!\n");
29
30 spin_lock(&lock1);
31 readcount--;
32 if(readcount==0)
33 up(&sem_w);
34 spin_unlock(&lock1);
35
36 return count;
37 }
38
39 int ThreadWrite(void *context)
40 {
41 spin_lock(&lock1);
42 writecount++;
43 if(writecount==1)
44 down(&sem_r);
45 spin_unlock(&lock1);
46
47 down(&sem_w);
48 writer++;
49 printk("<2>" "Writer %d is writting!\n",writer);
50 msleep(10);
51 printk("<2>" "Writer %d is over!\n",writer);
52 up(&sem_w);
53
54 spin_lock(&lock1);
55 writecount--;
56 if(writecount==0)
57 up(&sem_r);
58 spin_unlock(&lock1);
59
60 return count;
61 }
62
63 static __init int rwsem_init(void)
64 {
65 static int i;
66 init_MUTEX(&sem_r);
67 init_MUTEX(&sem_w);
68 for(i=0;i<2;i++){
69 kernel_thread(ThreadWrite,&i,CLONE_VM);
70 }
71
72 for(i=0;i<2;i++){
73 kernel_thread(ThreadRead,&i,CLONE_KERNEL);
74 }
75 for(i=2;i<5;i++){
76 kernel_thread(ThreadRead,&i,CLONE_KERNEL);
77 }
78 for(i=2;i<5;i++){
79 kernel_thread(ThreadWrite,&i,CLONE_KERNEL);
80 }
81
82 return 0;
83 }
84
85 static void rwsem_exit(void)
86 {
87
88 }
89
90 module_init(rwsem_init);
91 module_exit(rwsem_exit);
92
93 MODULE_AUTHOR("Mike Feng");

 

運行結果:

從結果上看,和我們上面的結果略有差別,因為我們這里實現的是寫者優先算法。

 

讀寫鎖:

 

       讀寫信號量的實現是基於讀寫鎖的。可以想到他們的應用都差不多。自旋鎖、讀寫鎖中不能有睡眠,我們就不做實驗驗證了,當你在鎖之間添加msleep函數時,會造成系統崩潰。

 

順序鎖:

 

       順序鎖和讀寫鎖非常相似,只是他為寫者賦予了較高的優先級:事實上,即使在讀者正在讀的時候也允許寫者繼續運行。這種策略的好處是寫者永遠不會等待(除非另外一個寫者正在寫),缺點是有些時候讀者不得不反復讀相同數據直到他獲得有效的副本。

 

 最后,為完整起見,附上代碼的Makefile文件:

 

View Code
1 obj-m+=semaphore.o down_read.o spinlock.o   
2 CURRENT:=$(shell pwd)
3 KERNEL_PATH:=/usr/src/kernels/$(shell uname -r)
4
5 all:
6 make -C $(KERNEL_PATH) M=$(CURRENT) modules
7 clean:
8 make -C $(KERNEL_PATH) M=$(CURRENT) clean

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM