一、並發與競態
並發是指一段時間內有多個程序執行,但任一個時刻點上只有一個程序在運行
並發就會導致一個問題:假設程序A對一個文件寫入3000個字符“a”,而另一個程序B對這個文件寫入3000個“b”,第三個程序C讀取這個文件,會導致讀取數據不一定是什么
因為可能在一段時間內先執行了A;當A執行到一半CPU切換到執行B了,這時就會導致數據混亂
解決這個問題的途徑是保證對共享資源的互斥訪問。如程序A向文件中寫入字符,那么B就無法訪問這個文件
訪問共享資源的代碼區稱為臨界區,它需要使用互斥機制保護,途徑有中斷屏蔽、原子操作、自旋鎖、信號量和互斥體等
在第二章點亮LED 中簡述過的readb()類函數就使用了內存屏蔽指令
二、中斷屏蔽
CPU一般都具備屏蔽中斷和打開中斷的功能,這項功能可以保證正在執行的內核執行路徑不被中斷處理程序所搶占,防止某些競態條件的發生。具體而言,中斷屏蔽使得中斷與進程之間的並發不再發生,而且,由於Linux內核的進程調度等操作都依賴中斷來實現,內核搶占進程之間的並發也得以避免了
使用方法如下:
local_irq_disable() /* 屏蔽中斷 */ ... /* 臨界區*/ ... local_irq_enable() /* 開中斷*/
為了保證系統運行,中斷屏蔽的時間盡量要少
三、原子操作
原子操作可以保證對一個整型數據的修改是排他性的。原子操作系列函數使用方法如下:
atomic_t v = ATOMIC_INIT(0); /* 定義原子變量v = 0 */ atomic_read(&v); /* 讀取原子變量值 */ atomic_add(i, &v); /* 原子變量 + i */ atomic_sub(i, &v); /* 原子變量 - i */ atomic_inc(&v); /* 原子變量 + 1 */ atomic_dec(&v); /* 原子變量 - 1 */ int atomic_inc_and_test(&v); /* 原子變量自加並測試是否為0,為0返回1 */ int atomic_dec_and_test(&v); /* 原子變量自減並測試是否為0 */ int atomic_sub_and_test(i, &v); /* 原子變量 - i后測試是否為0 */
示例如下:
1 /* 使用原子變量使設備只能被一個進程打開 */ 2 static atomic_t v = ATOMIC_INIT(1); 3 4 static int key_open(struct inode *nodep, struct file *filp) 5 { 6 if (!atomic_dec_and_test(&v)) /* 已經有進程打開 */ { 7 atomic_inc(&v); /* 重新加1 */ 8 return -EBUSY; 9 } 10 ... 11 } 12 13 static int key_release(struct inode *nodep, struct file *filp) 14 { 15 atomic_inc(&v); 16 ... 17 }
四、自旋鎖
自旋鎖(Spin Lock)是一種典型的對臨界資源進行互斥訪問的手段,工作原理是測試並設置(Test-And-Set)某個內存變量,如果此變量符合條件則退出
它有以下幾個特點:
1. 自旋鎖是忙等鎖。若鎖不可用,CPU會一直循環執行檢測,相當於while(1)。因此適用於臨界區代碼小的情況
2. 可能導致系統死鎖。比如遞歸調用自旋鎖
3. 鎖定期間不能調用可能導致進程調度的函數。如copy_to_user()和copy_from_user()
自旋鎖系列函數使用方法如下:
spinlock_t lock; /* 定義一個自旋鎖*/ spin_lock_init(&lock); /* 初始化一個自旋鎖*/ spin_lock(&lock); /* 獲取自旋鎖,保護臨界區 */ /* 臨界區*/ spin_unlock(&lock); /* 解鎖*/
示例如下:
1 /* 使用自旋鎖使設備只能被一個進程打開 */ 2 int count = 0; 3 4 static int key_open(struct inode *nodep, struct file *filp) 5 { 6 spin_lock(&lock); 7 if (count) /* 已經有進程打開 */ { 8 spin_unlock(&lock); 9 return -EBUSY; 10 } 11 ++count; 12 spin_unlock(&lock); 13 ... 14 } 15 16 static int key_release(struct inode *nodep, struct file *filp) 17 { 18 spin_lock(&lock); 19 --count; 20 spin_unlock(&lock); 21 ... 22 } 23 24 static int keys_init(void) 25 { 26 spinlock_t lock; 27 spin_lock_init(&lock); 28 ... 29 }
五、信號量
信號量使用方式類似於原子操作,系列函數使用方法如下:
struct semaphore sem; /* 定義一個信號量 */ /* 初始化信號量 = val */ void sema_init(struct semaphore *sem, int val); /* 獲得信號量,它會導致休眠,因此不能在中斷上下文中使用 */ void down(struct semaphore *sem); /* 獲得信號量,進入休眠狀態的進程能被信號打斷 */ int down_interruptible(struct semaphore *sem); /* 嘗試獲得信號量sem,如果能夠立刻獲得,它就獲得該信號量並返回0,否則返回非0值 * 它不會導致調用者睡眠,可以在中斷上下文中使用 */ int down_trylock(struct semaphore *sem); /* 釋放信號量 */ void up(struct semaphore *sem);
DEFINE_SEMAPHORE(sem)可以初始化信號量並賦值為1。如果是在Linux2.6.36版本以下,應使用DECLARE_MUTEX(sem)
示例如下:
1 /* 使用信號量使設備只能被一個進程打開 */ 2 static DEFINE_SEMAPHORE(lock); 3 4 static int key_open(struct inode *nodep, struct file *filp) 5 { 6 if (!down_trylock(&lock)) /* 已經有進程打開 */ { 7 return -EBUSY; 8 } 9 ... 10 } 11 12 static int key_release(struct inode *nodep, struct file *filp) 13 { 14 up(&lock); 15 ... 16 }
六、互斥體
互斥體系列函數使用方法如下:
struct mutex lock; /* 定義一個互斥體 */ mutex_init(&lock); /* 初始化互斥體 */ /* 獲取互斥體 */ void mutex_lock(struct mutex *lock); int mutex_lock_interruptible(struct mutex *lock); int mutex_trylock(struct mutex *lock); /* 釋放互斥體 */ void mutex_unlock(struct mutex *lock);
key源代碼(本示例通過互斥體防止copy_to_user()函數競態):

1 #include <linux/module.h> 2 #include <linux/fs.h> 3 #include <linux/init.h> 4 #include <linux/cdev.h> 5 #include <linux/slab.h> 6 #include <linux/device.h> 7 #include <linux/irq.h> 8 #include <linux/interrupt.h> 9 #include <linux/wait.h> 10 #include <linux/timer.h> 11 #include <linux/gpio.h> 12 #include <linux/sched.h> 13 #include <linux/mutex.h> 14 15 #include <asm/uaccess.h> 16 #include <asm/irq.h> 17 #include <asm/io.h> 18 19 #include <mach/gpio.h> 20 21 #define KEY_MAJOR 255 22 23 struct pin_desc { 24 int gpio; 25 int val; 26 char *name; 27 }; 28 29 struct key_device { 30 struct cdev cdev; 31 wait_queue_head_t r_head; 32 wait_queue_head_t w_head; 33 struct mutex lock; 34 }; 35 36 static struct pin_desc desc[4] = { 37 { EXYNOS4_GPX3(2), 0x01, "KEY0" }, 38 { EXYNOS4_GPX3(3), 0x02, "KEY1" }, 39 { EXYNOS4_GPX3(4), 0x03, "KEY2" }, 40 { EXYNOS4_GPX3(5), 0x04, "KEY3" }, 41 }; 42 43 static int g_major = KEY_MAJOR; 44 module_param(g_major, int, S_IRUGO); 45 46 static struct key_device* dev; 47 static struct class* scls; 48 static struct device* sdev; 49 static unsigned char key_val; 50 51 static irqreturn_t key_interrupt(int irq, void *dev_id) 52 { 53 struct pin_desc *pindesc = (struct pin_desc *)dev_id; 54 unsigned int tmp; 55 56 tmp = gpio_get_value(pindesc->gpio); 57 58 /* active low */ 59 printk(KERN_DEBUG "KEY %d: %08x\n", pindesc->val, tmp); 60 61 if (tmp) 62 key_val = pindesc->val; 63 else 64 key_val = pindesc->val | 0x80; 65 66 set_current_state(TASK_RUNNING); 67 68 return IRQ_HANDLED; 69 } 70 71 static ssize_t key_read(struct file *filp, char __user *buf, size_t len, loff_t * loff) 72 { 73 struct key_device *dev = filp->private_data; 74 75 // 聲明等待隊列 76 DECLARE_WAITQUEUE(rwait, current); 77 mutex_lock(&dev->lock); // 上鎖 78 add_wait_queue(&dev->r_head, &rwait); 79 80 // 休眠 81 __set_current_state(TASK_INTERRUPTIBLE); 82 mutex_unlock(&dev->lock); // 解鎖 83 schedule(); 84 85 // 有數據 86 mutex_lock(&dev->lock); // 上鎖 87 copy_to_user(buf, &key_val, 1); 88 mutex_unlock(&dev->lock); // 解鎖 89 90 remove_wait_queue(&dev->r_head, &rwait); 91 set_current_state(TASK_RUNNING); 92 93 return 1; 94 } 95 96 static int key_open(struct inode *nodep, struct file *filp) 97 { 98 struct key_device *dev = container_of(nodep->i_cdev, struct key_device, cdev); 99 // 放入私有數據中 100 filp->private_data = dev; 101 102 int irq; 103 int i, err = 0; 104 105 for (i = 0; i < ARRAY_SIZE(desc); i++) { 106 if (!desc[i].gpio) 107 continue; 108 109 irq = gpio_to_irq(desc[i].gpio); 110 err = request_irq(irq, key_interrupt, IRQ_TYPE_EDGE_BOTH, 111 desc[i].name, (void *)&desc[i]); 112 if (err) 113 break; 114 } 115 116 if (err) { 117 i--; 118 for (; i >= 0; i--) { 119 if (!desc[i].gpio) 120 continue; 121 122 irq = gpio_to_irq(desc[i].gpio); 123 free_irq(irq, (void *)&desc[i]); 124 } 125 return -EBUSY; 126 } 127 128 init_waitqueue_head(&dev->r_head); 129 mutex_init(&dev->lock); // 初始化 130 131 return 0; 132 } 133 134 static int key_release(struct inode *nodep, struct file *filp) 135 { 136 // 釋放中斷 137 int irq, i; 138 139 for (i = 0; i < ARRAY_SIZE(desc); i++) { 140 if (!desc[i].gpio) 141 continue; 142 143 irq = gpio_to_irq(desc[i].gpio); 144 free_irq(irq, (void *)&desc[i]); 145 } 146 147 return 0; 148 } 149 150 static struct file_operations key_fops = { 151 .owner = THIS_MODULE, 152 .read = key_read, 153 .open = key_open, 154 .release = key_release, 155 }; 156 157 static int keys_init(void) 158 { 159 int ret; 160 int devt; 161 if (g_major) { 162 devt = MKDEV(g_major, 0); 163 ret = register_chrdev_region(devt, 1, "key"); 164 } 165 else { 166 ret = alloc_chrdev_region(&devt, 0, 1, "key"); 167 g_major = MAJOR(devt); 168 } 169 170 if (ret) 171 return ret; 172 173 dev = kzalloc(sizeof(struct key_device), GFP_KERNEL); 174 if (!dev) { 175 ret = -ENOMEM; 176 goto fail_alloc; 177 } 178 179 cdev_init(&dev->cdev, &key_fops); 180 ret = cdev_add(&dev->cdev, devt, 1); 181 if (ret) 182 return ret; 183 184 scls = class_create(THIS_MODULE, "key"); 185 sdev = device_create(scls, NULL, devt, NULL, "key"); 186 187 return 0; 188 189 fail_alloc: 190 unregister_chrdev_region(devt, 1); 191 192 return ret; 193 } 194 195 static void keys_exit(void) 196 { 197 dev_t devt = MKDEV(g_major, 0); 198 199 device_destroy(scls, devt); 200 class_destroy(scls); 201 202 cdev_del(&(dev->cdev)); 203 kfree(dev); 204 205 unregister_chrdev_region(devt, 1); 206 } 207 208 module_init(keys_init); 209 module_exit(keys_exit); 210 211 MODULE_LICENSE("GPL");
Makefile:

1 KERN_DIR = /work/tiny4412/tools/linux-3.5 2 3 all: 4 make -C $(KERN_DIR) M=`pwd` modules 5 6 clean: 7 make -C $(KERN_DIR) M=`pwd` modules clean 8 rm -rf modules.order 9 10 obj-m += key.o
測試文件:

1 #include <stdio.h> 2 #include <unistd.h> 3 #include <sys/types.h> 4 #include <sys/stat.h> 5 #include <fcntl.h> 6 #include <string.h> 7 8 int main(int argc, char** argv) 9 { 10 int fd; 11 fd = open("/dev/key", O_RDWR); 12 if (fd < 0) { 13 printf("can't open /dev/key\n"); 14 return -1; 15 } 16 17 unsigned char key_val; 18 19 while (1) { 20 read(fd, &key_val, 1); 21 printf("key_val = 0x%x\n", key_val); 22 } 23 24 close(fd); 25 26 return 0; 27 }
七、互斥體和自旋鎖的選擇
通過比較可知,互斥體的作用於自旋鎖類似。那么什么時候使用互斥體,什么時候使用自旋鎖呢?
1. 若臨界區較小,宜使用自旋鎖;若臨界區較大,宜使用互斥體
2. 互斥體所保護的臨界區可以出現包含阻塞(進程切換)的代碼,但是自旋鎖不可以
3. 互斥體存在於進程上下文;如果要在中斷中使用,宜使用自旋鎖
下一章 6、異步通知