六、设备驱动中的并发控制(二)

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);

 

posted @ 2019-10-17 11:12  游戏进行中  阅读(253)  评论(0编辑  收藏  举报