六、设备驱动中的并发控制(二)
6.4 信号量
信号量(semaphore)是操作系统中最典型的用于同步和互斥的手段,信号量的值可以是 0,1 或者 n。信号量与操作系统的经典概念 PV 操作对应。
- P(S):
- 将信号量 S 的值减 1,即 S = S - 1
- 如果 S >= 0,则该进程继续执行,否则该进程置为等待状态,排入等待队列
- V(S):
- 将信号量 S 的值加 1,即 S= S + 1
- 如果 S > 0,唤醒队列中等待信号量的进程
1 /* Please don't access any members of this structure directly 2 * 定义信号量:struct semaphore sem; 3 */ 4 struct semaphore { 5 raw_spinlock_t lock; 6 unsigned int count; 7 struct list_head wait_list; 8 };
1 /** 2 * 初始化信号量 3 * 该函数初始化信号量,并设置信号量 sem 的值为 val 4 */ 5 static inline void sema_init(struct semaphore *sem, int val) 6 { 7 static struct lock_class_key __key; 8 *sem = (struct semaphore) __SEMAPHORE_INITIALIZER(*sem, val); 9 lockdep_init_map(&sem->lock.dep_map, "semaphore->lock", &__key, 0); 10 }
1 /** 2 * 获得信号量: 3 * 用于获得信号量 sem,它会导致睡眠,因此不能再中断上下文中使用 4 * 进入睡眠状态的进程不能被信号打断 5 */ 6 extern void down(struct semaphore *sem);
1 /** 2 * 获得信号量: 3 * 与 down 函数类似,不同的是进入睡眠状态的进程能被信号打断, 4 * 信号也会导致该函数返回,这时候函数的返回值非 0 5 * 在使用此函数的时候,对返回值一般会进行检查,如果非 0,通常立即返回 -ERESTARTSYS 6 * if(down_interruptible(&sem)) 7 * return -ERESTARTSYS; 8 */ 9 extern int __must_check down_interruptible(struct semaphore *sem);
1 /** 2 * 获得信号量: 3 * 尝试获得信号量,如果能够立刻获得,它就获得信号量并返回 0,否则返回非 0 值 4 * 它不会导致调用者睡眠,可以再中断上下文中使用 5 */ 6 extern int __must_check down_trylock(struct semaphore *sem);
1 /** 2 * 释放信号量,唤醒等待者 3 */ 4 extern void up(struct semaphore *sem);
信号量作为一种可能互斥手段,可以保护临界区,使用方式与自旋锁类似。与自旋锁相同的是,只有得到信号量的值才能执行临界区的代码。
与自旋锁不同的是,当获取不到信号量时,进程不会原地打转而是进入休眠等待状态。
用作互斥时,信号量一般这样使用:
因为内核更倾向于直接使用 mutex 作为互斥手段,所以不推荐信号量如此使用。
信号量更适合用于同步,比如具体数值的生产者\消费者问题。
一个进程 A 执行 down() 等待信号量,另一个进程 B 执行 up() 释放信号量,这样进程 A 就同步等待了进程 B。
6.5 互斥体
1 /** 2 * 定义互斥体:struct mutex my_mutex; 3 */ 4 struct mutex { 5 /* 1: unlocked, 0: locked, negative: locked, possible waiters */ 6 atomic_t count; 7 spinlock_t wait_lock; 8 struct list_head wait_list; 9 #if defined(CONFIG_DEBUG_MUTEXES) || defined(CONFIG_MUTEX_SPIN_ON_OWNER) 10 struct task_struct *owner; 11 #endif 12 #ifdef CONFIG_MUTEX_SPIN_ON_OWNER 13 struct optimistic_spin_queue osq; /* Spinner MCS lock */ 14 #endif 15 #ifdef CONFIG_DEBUG_MUTEXES 16 void *magic; 17 #endif 18 #ifdef CONFIG_DEBUG_LOCK_ALLOC 19 struct lockdep_map dep_map; 20 #endif 21 };
1 /** 2 * mutex_init - initialize the mutex 3 * 初始化互斥体:mutex_init(&my_mutex); 4 * @mutex: the mutex to be initialized 5 * 6 * Initialize the mutex to unlocked state. 7 * 8 * It is not allowed to initialize an already locked mutex. 9 */ 10 # define mutex_init(mutex) \ 11 do { \ 12 static struct lock_class_key __key; \ 13 \ 14 __mutex_init((mutex), #mutex, &__key); \ 15 } while (0)
1 /** 2 * 获取互斥体 3 * 引起的睡眠不能被信号打断。 4 */ 5 extern void mutex_lock(struct mutex *lock); 6 /** 7 * 获取互斥体 8 * 引起的睡眠可被信号打断。 9 */ 10 extern int __must_check mutex_lock_interruptible(struct mutex *lock);
1 /* 2 * 获取互斥体 3 * 尝试获得 mutex,获取不到 mutex 时不会引起进程睡眠 4 * NOTE: mutex_trylock() follows the spin_trylock() convention, 5 * not the down_trylock() convention! 6 * 7 * Returns 1 if the mutex has been acquired successfully, and 0 on contention. 8 */ 9 extern int mutex_trylock(struct mutex *lock);
1 /** 释放互斥体 */ 2 extern void mutex_unlock(struct mutex *lock);
用法:
1 /** 2 * 如何用互斥体: 3 * 定义互斥体 4 * struct mutex my_mutex; 5 * 初始化互斥体 6 * mutex_init(&my_mutex); 7 * 获取互斥体 8 * mutex_lock(&my_mutex); 9 * 临界资源 10 * ... 11 * 释放 mutex 12 * mutex_unlock(&my_mutex); 13 */
- 自旋锁和互斥体的区别:
- 属于不同层次互斥手段,互斥体的实现依赖于自旋锁,自旋锁属于更底层的手段
- 互斥体是进程级的,用于多个进程之间对资源的互斥,虽也在内核中,但内核执行路径是以进程的身份,代表进程来抢夺资源。若竞争失败,会发生进程上下文切换,当前进程进入睡眠状态,CPU 将运行其他进程。由于进程上下文切换开销大,因此只有当进程占用资源时间较长时,用互斥体才是最好的选择。临界区访问时间较短则用自旋锁
- 互斥体所保护的临界区可包含可能引起阻塞的代码,而自旋锁要绝对避免此情况。
- 互斥体用于进程上下文,对于中断和软中断只能使用自旋锁。
6.6 完成量
完成量(completion)用于一个执行单元等待另一个执行单元执行完某事。
1 /** 2 * 定义完成量:struct completion my_completion; 3 */ 4 struct completion { 5 unsigned int done; 6 wait_queue_head_t wait; 7 };
1 /** 2 * init_completion - Initialize a dynamically allocated completion 3 * 初始化完成量的值为 0(即没有完成的状态) 4 * @x: pointer to completion structure that is to be initialized 5 * 6 * This inline function will initialize a dynamically created completion 7 * structure. 8 */ 9 static inline void init_completion(struct completion *x) 10 { 11 x->done = 0; 12 init_waitqueue_head(&x->wait); 13 }
1 /** 2 * reinit_completion - reinitialize a completion structure 3 * 重新初始化完成量的值为 0(即没有完成的状态) 4 * @x: pointer to completion structure that is to be reinitialized 5 * 6 * This inline function should be used to reinitialize a completion structure so it can 7 * be reused. This is especially important after complete_all() is used. 8 */ 9 static inline void reinit_completion(struct completion *x) 10 { 11 x->done = 0; 12 }
1 /** 等待一个完成量被唤醒 */ 2 extern void wait_for_completion(struct completion *);
1 /** 唤醒完成量 */ 2 extern void complete(struct completion *); ///< 只唤醒一个等待的执行单元 3 extern void complete_all(struct completion *);///< 释放所有等待同一完成量的执行单元
6.7 globalmem 增加并发
在 globalmem 的读写函数中,要调用 copy_from_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/uaccess.h> 7 #include <linux/mutex.h> 8 9 #define GLOBALMEM_SIZE 0x1000 10 //#define MEM_CLEAR 0X1 11 #define GLOBALMEM_MAGIC 'g' 12 #define MEM_CLEAR _IO(GLOBALMEM_MAGIC, 0) 13 #define GLOBALMEM_MAJOR 230 14 #define DEVICE_NUMBER 10 15 16 static int globalmem_major = GLOBALMEM_MAJOR; 17 module_param(globalmem_major, int, S_IRUGO); 18 19 struct globalmem_dev { 20 struct cdev cdev; 21 unsigned char mem[GLOBALMEM_SIZE]; 22 struct mutex mutex; 23 }; 24 25 struct globalmem_dev *globalmem_devp; 26 27 /** 28 * 这里涉及到私有数据的定义,大多数遵循将文件私有数据 pirvate_data 指向设备结构体, 29 * 再用 read write llseek ioctl 等函数通过 private_data 访问设备结构体。 30 * 对于此驱动而言,私有数据的设置是在 open 函数中完成的 31 */ 32 static int globalmem_open(struct inode *inode, struct file *filp) 33 { 34 /** 35 * NOTA: 36 * container_of 的作用是通过结构体成员的指针找到对应结构体的指针。 37 * 第一个参数是结构体成员的指针 38 * 第二个参数是整个结构体的类型 39 * 第三个参数为传入的第一个参数(即结构体成员)的类型 40 * container_of 返回值为整个结构体指针 41 */ 42 struct globalmem_dev *dev = container_of(inode->i_cdev, struct globalmem_dev, cdev); 43 filp->private_data = dev; 44 return 0; 45 } 46 47 static int globalmem_release(struct inode *inode, struct file *filp) 48 { 49 return 0; 50 } 51 52 static long globalmem_ioctl(struct file *filp, unsigned int cmd, unsigned long arg) 53 { 54 struct globalmem_dev *dev = filp->private_data; 55 56 switch(cmd){ 57 case MEM_CLEAR: 58 mutex_lock(&dev->mutex); 59 memset(dev->mem, 0, GLOBALMEM_SIZE); 60 printk(KERN_INFO "globalmem is set to zero\n"); 61 mutex_unlock(&dev->mutex); 62 break; 63 default: 64 return -EINVAL; 65 } 66 67 return 0; 68 } 69 70 static loff_t globalmem_llseek(struct file *filp, loff_t offset, int orig) 71 { 72 loff_t ret = 0; 73 switch(orig) { 74 case 0: /** 从文件开头位置 seek */ 75 if(offset < 0){ 76 ret = -EINVAL; 77 break; 78 } 79 if((unsigned int)offset > GLOBALMEM_SIZE){ 80 ret = -EINVAL; 81 break; 82 } 83 filp->f_pos = (unsigned int)offset; 84 ret = filp->f_pos; 85 break; 86 case 1: /** 从文件当前位置开始 seek */ 87 if((filp->f_pos + offset) > GLOBALMEM_SIZE){ 88 ret = -EINVAL; 89 break; 90 } 91 if((filp->f_pos + offset) < 0){ 92 ret = -EINVAL; 93 break; 94 } 95 filp->f_pos += offset; 96 ret = filp->f_pos; 97 break; 98 default: 99 ret = -EINVAL; 100 break; 101 } 102 103 return ret; 104 } 105 106 static ssize_t globalmem_write(struct file *filp, const char __user *buf, size_t size, loff_t *ppos) 107 { 108 unsigned long p = *ppos; 109 unsigned int count = size; 110 int ret = 0; 111 struct globalmem_dev *dev = filp->private_data; 112 113 if(p >= GLOBALMEM_SIZE) 114 return 0; 115 if(count > GLOBALMEM_SIZE - p) 116 count = GLOBALMEM_SIZE - p; 117 118 mutex_lock(&dev->mutex); 119 if(copy_from_user(dev->mem + p, buf, count)) 120 ret = -EFAULT; 121 else { 122 123 *ppos += count; 124 ret = count; 125 printk(KERN_INFO "written %u bytes(s) from %lu\n", count, p); 126 } 127 mutex_unlock(&dev->mutex); 128 return ret; 129 } 130 131 /** 132 * *ppos 是要读的位置相对于文件开头的偏移,如果该偏移大于或等于 GLOBALMEM_SIZE,意味着已经独到文件末尾 133 */ 134 static ssize_t globalmem_read(struct file *filp, char __user *buf, size_t size, loff_t *ppos) 135 { 136 unsigned long p = *ppos; 137 unsigned int count = size; 138 int ret = 0; 139 struct globalmem_dev *dev = filp->private_data; 140 141 if(p >= GLOBALMEM_SIZE) 142 return 0; 143 if(count > GLOBALMEM_SIZE - p) 144 count = GLOBALMEM_SIZE - p; 145 146 mutex_lock(&dev->mutex); 147 if(copy_to_user(buf, dev->mem + p, count)) { 148 ret = -EFAULT; 149 } else { 150 *ppos += count; 151 ret = count; 152 printk(KERN_INFO "read %u bytes(s) from %lu\n", count, p); 153 } 154 155 mutex_unlock(&dev->mutex); 156 return ret; 157 } 158 159 static const struct file_operations globalmem_fops = { 160 .owner = THIS_MODULE, 161 .llseek = globalmem_llseek, 162 .read = globalmem_read, 163 .write = globalmem_write, 164 .unlocked_ioctl = globalmem_ioctl, 165 .open = globalmem_open, 166 .release = globalmem_release, 167 }; 168 169 170 /** 171 * @brief globalmem_setup_cdev 172 * 173 * @param dev 174 * @param index 次设备号 175 */ 176 static void globalmem_setup_cdev(struct globalmem_dev *dev, int index) 177 { 178 int err; 179 int devno = MKDEV(globalmem_major, index); 180 181 /** 使用 cdev_init 即是静态初始化了 cdev */ 182 cdev_init(&dev->cdev, &globalmem_fops); 183 dev->cdev.owner = THIS_MODULE; 184 185 /** 设备编号范围设置为1,表示我们只申请了一个设备 */ 186 err = cdev_add(&dev->cdev, devno, 1); 187 if(err) 188 printk(KERN_NOTICE "Error %d adding globalmem%d\n", err, index); 189 } 190 191 static int __init globalmem_init(void) 192 { 193 int ret; 194 int i; 195 dev_t devno = MKDEV(globalmem_major, 0); 196 197 if(globalmem_major) 198 ret = register_chrdev_region(devno, DEVICE_NUMBER, "globalmem"); 199 else { 200 ret = alloc_chrdev_region(&devno, 0, DEVICE_NUMBER, "gobalmem"); 201 globalmem_major = MAJOR(devno); 202 } 203 204 if(ret < 0) 205 return ret; 206 207 globalmem_devp = kzalloc(sizeof(struct globalmem_dev), GFP_KERNEL); 208 if(!globalmem_devp){ 209 ret = -ENOMEM; 210 goto fail_malloc; 211 } 212 213 mutex_init(&globalmem_devp->mutex); 214 for(i = 0; i < DEVICE_NUMBER; i++){ 215 globalmem_setup_cdev(globalmem_devp + i, i); 216 } 217 218 fail_malloc: 219 unregister_chrdev_region(devno, 1); 220 return ret; 221 } 222 223 static void __exit globalmem_exit(void) 224 { 225 int i; 226 for(i = 0; i < DEVICE_NUMBER; i++) { 227 cdev_del(&(globalmem_devp + i)->cdev); 228 } 229 kfree(globalmem_devp); 230 unregister_chrdev_region(MKDEV(globalmem_major, 0), 1); 231 } 232 233 module_init(globalmem_init); 234 module_exit(globalmem_exit);