5、並發控制


 

一、並發與競態

並發是指一段時間內有多個程序執行,但任一個時刻點上只有一個程序在運行

並發就會導致一個問題:假設程序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");
View Code

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
View Code

測試文件:

 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 }
View Code

 

 

七、互斥體和自旋鎖的選擇

通過比較可知,互斥體的作用於自旋鎖類似。那么什么時候使用互斥體,什么時候使用自旋鎖呢?

1. 若臨界區較小,宜使用自旋鎖;若臨界區較大,宜使用互斥體

2. 互斥體所保護的臨界區可以出現包含阻塞(進程切換)的代碼,但是自旋鎖不可以

3. 互斥體存在於進程上下文;如果要在中斷中使用,宜使用自旋鎖

 

 

下一章  6、異步通知

 

 


免責聲明!

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



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