免费注册 查看新帖 |

Chinaunix

  平台 论坛 博客 文库
1234下一页
最近访问板块 发新帖
查看: 25617 | 回复: 39
打印 上一主题 下一主题

Linux驱动开发庖丁解牛之四——并发控制之信号量(1) [复制链接]

论坛徽章:
3
金牛座
日期:2014-06-14 22:04:062015年辞旧岁徽章
日期:2015-03-03 16:54:152015年迎新春徽章
日期:2015-03-04 09:49:45
跳转到指定楼层
1 [收藏(0)] [报告]
发表于 2009-11-10 22:34 |只看该作者 |倒序浏览
并发控制,是多任务操作系统必须面临和解决的一个问题。并发与互斥,主要是用于保护临界资源,如果不站在操作系统进程调度的角度,就很难理解并发与互斥的概念和应用。无论是抢占式操作系统,还是分时操作系统,对于临界资源的保护,都必须采用互斥的机制。Linux内核中,有多种并发控制的机制:自旋锁、原子变量、信号量、读写锁等等。不同的并发机制对应于不同的应用场合,比如说,自旋锁可以应用到中断处理函数中,信号量则不可以。本文主要从一个globalmem_lock例子来阐述信号量的使用。注:该例子取自《Linux设备驱动开发详解》,并做了编译告警的一些改动。
  1. /*======================================================================
  2.     A globalmem driver as an example of char device drivers  
  3.     This example is to introduce how to use locks to avoid race conditions
  4.    
  5.     The initial developer of the original code is Baohua Song
  6.     <[email]author@linuxdriver.cn[/email]>. All Rights Reserved.
  7. ======================================================================*/
  8. #include <linux/module.h>
  9. #include <linux/types.h>
  10. #include <linux/fs.h>
  11. #include <linux/errno.h>
  12. #include <linux/mm.h>
  13. #include <linux/sched.h>
  14. #include <linux/init.h>
  15. #include <linux/cdev.h>
  16. #include <asm/io.h>
  17. #include <asm/system.h>
  18. #include <asm/uaccess.h>

  19. #define GLOBALMEM_SIZE        0x1000        /*全局内存最大4K字节*/
  20. #define MEM_CLEAR 0x1  /*清0全局内存*/
  21. //#define GLOBALMEM_MAJOR 254    /*预设的globalmem的主设备号*/
  22. #define GLOBALMEM_MAJOR 0 //[color=Red]我这里设为0,主要是为了让系统自动分配主设备号,如果不这样,有些系统可能insmod不成功[/color]

  23. static globalmem_major = GLOBALMEM_MAJOR;
  24. /*globalmem设备结构体*/
  25. struct globalmem_dev                                    
  26. {                                                        
  27.   struct cdev cdev; /*cdev结构体*/                       
  28.   unsigned char mem[GLOBALMEM_SIZE]; /*全局内存*/     
  29.   struct semaphore sem; /*并发控制用的信号量*/   
  30. };

  31. struct globalmem_dev *globalmem_devp; /*设备结构体指针*/

  32. /*文件打开函数*/
  33. int globalmem_open(struct inode *inode, struct file *filp)
  34. {
  35.   /*将设备结构体指针赋值给文件私有数据指针*/
  36.   filp->private_data = globalmem_devp;
  37.   return 0;
  38. }

  39. /*文件释放函数*/
  40. int globalmem_release(struct inode *inode, struct file *filp)
  41. {
  42.   return 0;
  43. }

  44. /* ioctl设备控制函数 */
  45. static int globalmem_ioctl(struct inode *inodep, struct file *filp, unsigned
  46.   int cmd, unsigned long arg)
  47. {
  48.   struct globalmem_dev *dev = filp->private_data; /*获得设备结构体指针*/

  49.   switch (cmd)
  50.   {
  51.     case MEM_CLEAR:
  52.       if (down_interruptible(&dev->sem))
  53.       {
  54.         return  - ERESTARTSYS;
  55.       }
  56.       memset(dev->mem, 0, GLOBALMEM_SIZE);
  57.       up(&dev->sem); //释放信号量

  58.       printk(KERN_INFO "globalmem is set to zero\n");
  59.       break;

  60.     default:
  61.       return  - EINVAL;
  62.   }
  63.   return 0;
  64. }

  65. /*读函数*/
  66. static ssize_t globalmem_read(struct file *filp, char __user *buf, size_t size,
  67.   loff_t *ppos)
  68. {
  69.   unsigned long p =  *ppos;
  70.   unsigned int count = size;
  71.   int ret = 0;
  72.   struct globalmem_dev *dev = filp->private_data; /*获得设备结构体指针*/

  73.   /*分析和获取有效的写长度*/
  74.   if (p >= GLOBALMEM_SIZE)
  75.     return count ?  - ENXIO: 0;
  76.   if (count > GLOBALMEM_SIZE - p)
  77.     count = GLOBALMEM_SIZE - p;

  78.   if (down_interruptible(&dev->sem))
  79.   {
  80.     return  - ERESTARTSYS;
  81.   }

  82.   /*内核空间->用户空间*/
  83.   if (copy_to_user(buf, (void*)(dev->mem + p), count))
  84.   {
  85.     ret =  - EFAULT;
  86.   }
  87.   else
  88.   {
  89.     *ppos += count;
  90.     ret = count;

  91.     printk(KERN_INFO "read %u bytes(s) from %lu\n", count, p);
  92.   }
  93.   up(&dev->sem); //释放信号量

  94.   return ret;
  95. }

  96. /*写函数*/
  97. static ssize_t globalmem_write(struct file *filp, const char __user *buf,
  98.   size_t size, loff_t *ppos)
  99. {
  100.   unsigned long p =  *ppos;
  101.   unsigned int count = size;
  102.   int ret = 0;
  103.   struct globalmem_dev *dev = filp->private_data; /*获得设备结构体指针*/

  104.   /*分析和获取有效的写长度*/
  105.   if (p >= GLOBALMEM_SIZE)
  106.     return count ?  - ENXIO: 0;
  107.   if (count > GLOBALMEM_SIZE - p)
  108.     count = GLOBALMEM_SIZE - p;

  109.   if (down_interruptible(&dev->sem))//获得信号量
  110.   {
  111.     return  - ERESTARTSYS;
  112.   }
  113.   /*用户空间->内核空间*/
  114.   if (copy_from_user(dev->mem + p, buf, count))
  115.     ret =  - EFAULT;
  116.   else
  117.   {
  118.     *ppos += count;
  119.     ret = count;

  120.     printk(KERN_INFO "written %u bytes(s) from %lu\n", count, p);
  121.   }
  122.   up(&dev->sem); //释放信号量
  123.   return ret;
  124. }

  125. /* seek文件定位函数 */
  126. static loff_t globalmem_llseek(struct file *filp, loff_t offset, int orig)
  127. {
  128.   loff_t ret = 0;
  129.   switch (orig)
  130.   {
  131.     case 0:   /*相对文件开始位置偏移*/
  132.       if (offset < 0)
  133.       {
  134.         ret =  - EINVAL;
  135.         break;
  136.       }
  137.       if ((unsigned int)offset > GLOBALMEM_SIZE)
  138.       {
  139.         ret =  - EINVAL;
  140.         break;
  141.       }
  142.       filp->f_pos = (unsigned int)offset;
  143.       ret = filp->f_pos;
  144.       break;
  145.     case 1:   /*相对文件当前位置偏移*/
  146.       if ((filp->f_pos + offset) > GLOBALMEM_SIZE)
  147.       {
  148.         ret =  - EINVAL;
  149.         break;
  150.       }
  151.       if ((filp->f_pos + offset) < 0)
  152.       {
  153.         ret =  - EINVAL;
  154.         break;
  155.       }
  156.       filp->f_pos += offset;
  157.       ret = filp->f_pos;
  158.       break;
  159.     default:
  160.       ret =  - EINVAL;
  161.       break;
  162.   }
  163.   return ret;
  164. }

  165. /*文件操作结构体*/
  166. static const struct file_operations globalmem_fops =
  167. {
  168.   .owner = THIS_MODULE,
  169.   .llseek = globalmem_llseek,
  170.   .read = globalmem_read,
  171.   .write = globalmem_write,
  172.   .ioctl = globalmem_ioctl,
  173.   .open = globalmem_open,
  174.   .release = globalmem_release,
  175. };

  176. /*初始化并注册cdev*/
  177. static void globalmem_setup_cdev(struct globalmem_dev *dev, int index)
  178. {
  179.   int err, devno = MKDEV(globalmem_major, index);

  180.   cdev_init(&dev->cdev, &globalmem_fops);
  181.   dev->cdev.owner = THIS_MODULE;
  182.   dev->cdev.ops = &globalmem_fops;
  183.   err = cdev_add(&dev->cdev, devno, 1);
  184.   if (err)
  185.     printk(KERN_NOTICE "Error %d adding LED%d", err, index);
  186. }

  187. /*设备驱动模块加载函数*/
  188. int globalmem_init(void)
  189. {
  190.   int result;
  191.   dev_t devno = MKDEV(globalmem_major, 0);

  192.   /* 申请设备号*/
  193.   if (globalmem_major)
  194.     result = register_chrdev_region(devno, 1, "globalmem");
  195.   else  /* 动态申请设备号 */
  196.   {
  197.     result = alloc_chrdev_region(&devno, 0, 1, "globalmem");
  198.     globalmem_major = MAJOR(devno);
  199.   }  
  200.   if (result < 0)
  201.     return result;
  202.    
  203.   /* 动态申请设备结构体的内存*/
  204.   globalmem_devp = kmalloc(sizeof(struct globalmem_dev), GFP_KERNEL);
  205.   if (!globalmem_devp)    /*申请失败*/
  206.   {
  207.     result =  - ENOMEM;
  208.     goto fail_malloc;
  209.   }
  210.   memset(globalmem_devp, 0, sizeof(struct globalmem_dev));
  211.   
  212.   globalmem_setup_cdev(globalmem_devp, 0);
  213.   init_MUTEX(&globalmem_devp->sem);   /*初始化信号量*/  
  214.   printk("register globalmem success\n");
  215.   return 0;

  216.   fail_malloc: unregister_chrdev_region(devno, 1);
  217.   return result;
  218. }

  219. /*模块卸载函数*/
  220. void globalmem_exit(void)
  221. {
  222.   cdev_del(&globalmem_devp->cdev);   /*注销cdev*/
  223.   kfree(globalmem_devp);     /*释放设备结构体内存*/
  224.   unregister_chrdev_region(MKDEV(globalmem_major, 0), 1); /*释放设备号*/
  225.   printk("unregister globalmem success\n");
  226. }

  227. MODULE_AUTHOR("Song Baohua");
  228. MODULE_LICENSE("Dual BSD/GPL");

  229. module_param(globalmem_major, int, S_IRUGO);

  230. module_init(globalmem_init);
  231. module_exit(globalmem_exit);
复制代码


Makefile:
  1. TARGET = globalmem_lock
  2. KDIR = /lib/modules/$(shell uname -r)/build
  3. PWD = $(shell pwd)
  4. obj-m := $(TARGET).o
  5. default:
  6.         make -C $(KDIR) M=$(PWD) modules
  7. clean:
  8.         $(RM) *.o *.ko *.mod.c Module.symvers modules.order
复制代码

[ 本帖最后由 dreamice 于 2009-11-11 09:21 编辑 ]

评分

参与人数 2可用积分 +60 收起 理由
godbach + 30 精品文章
T-Bagwell + 30 原创,重重的赏

查看全部评分

论坛徽章:
3
金牛座
日期:2014-06-14 22:04:062015年辞旧岁徽章
日期:2015-03-03 16:54:152015年迎新春徽章
日期:2015-03-04 09:49:45
2 [报告]
发表于 2009-11-10 22:59 |只看该作者

回复 #1 dreamice 的帖子

在上文中,我们看到在读写函数中,用到了down_interruptible()和up()
在并发控制中,我们看到,对临界资源的保护,总是成对的函数在操作,一个是进入临界区的操作,一个是出临界区的操作。在这里,down_interruptible就是进入临界区的操作,因为为了防止并发的写操作,所以在写时,进行信号量的获取。了解一种实现机制,我们最好是走进源代码去,而Linux这个开源的操作系统,更是给我们提供了便捷的方式。
down_interruptible(&dev->sem)
他的参数是dev的成员,我们来看一看这个成员:
struct globalmem_dev                                    
{                                                        
  struct cdev cdev; /*cdev结构体*/                       
  unsigned char mem[GLOBALMEM_SIZE]; /*全局内存大小*/     
  struct semaphore sem; /*并发控制用的信号量*/   
};
他在模块初始化函数中被初始化:
init_MUTEX(&globalmem_devp->sem);   /*初始化信号量*/
先看看struct semaphore 这个结构体的定义:
/* Please don't access any members of this structure directly */
struct semaphore {
        spinlock_t                lock;
        unsigned int                count;
        struct list_head        wait_list;
};
很简单,就3个成员。lock是自旋锁类型的,count是信号量的计数,wait_list是一个双向链表结构的等待队列,即当需要等待信号量时,调用进程把自己加入到等待队列中,然后进入睡眠状态。

接下来我们看看init_MUTEX的实现:
#define init_MUTEX(sem)                sema_init(sem, 1)
static inline void sema_init(struct semaphore *sem, int val)
{
        static struct lock_class_key __key;
        *sem = (struct semaphore) __SEMAPHORE_INITIALIZER(*sem, val);
        lockdep_init_map(&sem->lock.dep_map, "semaphore->lock", &__key, 0);
}

#define __SEMAPHORE_INITIALIZER(name, n)                                \
{                                                                        \
        .lock                = __SPIN_LOCK_UNLOCKED((name).lock),                \
        .count                = n,                                                \
        .wait_list        = LIST_HEAD_INIT((name).wait_list),                \
}
由上面的调用函数关系可以看到,实际上就是把信号量的count值,置为1.

下面我们开始分析down_interruptible的实现:
int down_interruptible(struct semaphore *sem)
{
        unsigned long flags;
        int result = 0;

               /* 自旋锁,关中断,并保存当前的状态 */
        spin_lock_irqsave(&sem->lock, flags);
        if (likely(sem->count > 0))  /* 如果信号量大于0,说明此时临界资源是不被占用的,可以立即使用,开自旋锁,并返回。这是能拿到信号量的情况 */
                sem->count--;
        else                            /* 否则进入__down_interruptible(sem);中*/
                result = __down_interruptible(sem);
        spin_unlock_irqrestore(&sem->lock, flags);

        return result;
}

注:自旋锁在上述中,主要起的作用是对count值访问的保护。
如果拿不到信号量,我们看看__down_interruptible(sem);里面做了些什么
static noinline int __sched __down_interruptible(struct semaphore *sem)
{
        return __down_common(sem, TASK_INTERRUPTIBLE, MAX_SCHEDULE_TIMEOUT);
}
/*
* Because this function is inlined, the 'state' parameter will be
* constant, and thus optimised away by the compiler.  Likewise the
* 'timeout' parameter for the cases without timeouts.
*/
static inline int __sched __down_common(struct semaphore *sem, long state,
                                                                long timeout)
{
        struct task_struct *task = current;
        struct semaphore_waiter waiter;

        list_add_tail(&waiter.list, &sem->wait_list);
        waiter.task = task;
        waiter.up = 0;

        for (;;) {
                if (state == TASK_INTERRUPTIBLE && signal_pending(task))
                        goto interrupted;
                if (state == TASK_KILLABLE && fatal_signal_pending(task))
                        goto interrupted;
                if (timeout <= 0)
                        goto timed_out;
                __set_task_state(task, state);
                spin_unlock_irq(&sem->lock);
                timeout = schedule_timeout(timeout);
                spin_lock_irq(&sem->lock);
                if (waiter.up)
                        return 0;
        }

timed_out:
        list_del(&waiter.list);
        return -ETIME;

interrupted:
        list_del(&waiter.list);
        return -EINTR;
}

论坛徽章:
3
金牛座
日期:2014-06-14 22:04:062015年辞旧岁徽章
日期:2015-03-03 16:54:152015年迎新春徽章
日期:2015-03-04 09:49:45
3 [报告]
发表于 2009-11-10 23:33 |只看该作者

回复 #2 dreamice 的帖子

我们主要分析__sched __down_common的实现
因为拿不到信号量,则必然要等待信号量,于是,调用list_add_tail(&waiter.list, &sem->wait_list);将自己加入到等待队列中,进入for循环
我们传入的参数是 TASK_INTERRUPTIBLE,因此会进入if (state == TASK_INTERRUPTIBLE && signal_pending(task))这个条件,signal_pending即等待唤醒的信号量。
我们这里看到,程序还会检查一个timeout时间,这个是MAX_SCHEDULE_TIMEOUT,这个时间是((long)(~0UL>>1))。这个值比较特别,~0UL,在32位系统中,为0xffffffff,而右移1位,则变为
0x7fffffff了。__set_task_state(task, state);设置系统状态为TASK_INTERRUPTIBLE (可中断状态,参看进程状态相关说明),然后退出自旋锁,timeout = schedule_timeout(timeout);进行超时。我们看到,只有当waiter.up为真时,才会正常返回0,即获得了信号量。而其他,不管事超时,还是因为中断等原因导致其返回,都不是获得了需要的资源。回到调用处,我们也看到,如果down_interruptible返回非0,则说明调用失败。至此,down_interruptible我们也就简单的分析完了,下面,我们再看up操作:
/**
* up - release the semaphore
* @sem: the semaphore to release
*
* Release the semaphore.  Unlike mutexes, up() may be called from any
* context and even by tasks which have never called down().
*/
void up(struct semaphore *sem)
{
        unsigned long flags;

        spin_lock_irqsave(&sem->lock, flags);
        if (likely(list_empty(&sem->wait_list)))
                sem->count++;
        else
                __up(sem);
        spin_unlock_irqrestore(&sem->lock, flags);
}
从注释可以看到,up其实就是释放信号量。如果等待队列为空,则直接将信号量计数器加1即可,如果非空,则说明当前有进程在等待该信号量了,于是:
static noinline void __sched __up(struct semaphore *sem)
{
        struct semaphore_waiter *waiter = list_first_entry(&sem->wait_list,
                                                struct semaphore_waiter, list); /*找到等待该信号量的进程*/
        list_del(&waiter->list); /* 将该进程从等待队列中删除 */
        waiter->up = 1;
        wake_up_process(waiter->task); /* 唤醒该进程 */
}
由上可知,如果有进程在等待该信号量,信号量释放函数,将唤醒该等待的进程,这样,等待信号量的进程便可得到进入临界区的“钥匙”而进入运行状态了。

信号量的实现分析就这么简单。本文只是起到抛砖引玉的作用,即当我们去使用一个函数时,尽量从源代码去理解其实现机制。当你跟踪源代码时,你会发现很多相关联的部分,比如上面提到的进程状态。Linux内核是个庞大的结构,很多地方都互相依赖并存,因此,从一个细小的地方着手,就可以延伸和扩展很多的知识点。


总结和展望:
本文依从源代码和具体实例分析了信号量的实现机制,但没有在实际中体现信号量的作用,这一点我们可以从应用程序来验证信号量作用的体现,类似与PV操作等。后续将分析其他的并发控制机制,希望和大家一起共同交流,深入理解Linux内核实现的一些细节。

参考:
《Linux设备驱动开发详解》
《Linux设备驱动》
2009-11-10

论坛徽章:
3
金牛座
日期:2014-06-14 22:04:062015年辞旧岁徽章
日期:2015-03-03 16:54:152015年迎新春徽章
日期:2015-03-04 09:49:45
4 [报告]
发表于 2009-11-10 23:40 |只看该作者

回复 #3 dreamice 的帖子

附上例子的测试方法:
# cat /sys/module/globalmem_lock/parameters/globalmem_lock_major
250
# mknod /dev/globalmem c 250 0
# echo ‘hello dreamice’ > /dev/gloablmem_lock
# cat /dev/gloablmem_lock
hello dreamice

论坛徽章:
0
5 [报告]
发表于 2009-11-11 08:09 |只看该作者

论坛徽章:
2
申猴
日期:2013-12-26 22:11:31天秤座
日期:2014-12-23 10:23:19
6 [报告]
发表于 2009-11-11 10:07 |只看该作者
LZ啥时候给讲讲自旋锁和信号量之间区别和用途吧

论坛徽章:
3
金牛座
日期:2014-06-14 22:04:062015年辞旧岁徽章
日期:2015-03-03 16:54:152015年迎新春徽章
日期:2015-03-04 09:49:45
7 [报告]
发表于 2009-11-11 10:40 |只看该作者
原帖由 goter 于 2009-11-11 10:07 发表
LZ啥时候给讲讲自旋锁和信号量之间区别和用途吧


简单的理解,自旋锁属于“忙等”,而信号量是“睡等”,两者之间有点类似于轮询和中断的差别。因此,各自的就有相应的用途了。我们知道,中断和下半部都是不允许睡眠的,所以,在大家看到的Netfilter这个架构里面,互斥大多都使用的是自旋锁,而不是信号量。当然,忙等也有他的缺点,那就是占用了CPU的执行时间,在一些需要和应用层交互的内核模块中,就最好不要使用这样的机制,而采用信号量或者读写锁等机制,这样可以让进程睡眠,等待资源。

论坛徽章:
0
8 [报告]
发表于 2009-11-11 11:51 |只看该作者

回复 #7 dreamice 的帖子

说的话!在很多书上多说过!!觉得最好还是用程序说话 ,是最好解答!!要累坏LZ了!

论坛徽章:
3
金牛座
日期:2014-06-14 22:04:062015年辞旧岁徽章
日期:2015-03-03 16:54:152015年迎新春徽章
日期:2015-03-04 09:49:45
9 [报告]
发表于 2009-11-11 12:00 |只看该作者

回复 #8 xiaochangfu 的帖子

多看内核源代码,呵呵

论坛徽章:
36
IT运维版块每日发帖之星
日期:2016-04-10 06:20:00IT运维版块每日发帖之星
日期:2016-04-16 06:20:0015-16赛季CBA联赛之广东
日期:2016-04-16 19:59:32IT运维版块每日发帖之星
日期:2016-04-18 06:20:00IT运维版块每日发帖之星
日期:2016-04-19 06:20:00每日论坛发贴之星
日期:2016-04-19 06:20:00IT运维版块每日发帖之星
日期:2016-04-25 06:20:00IT运维版块每日发帖之星
日期:2016-05-06 06:20:00IT运维版块每日发帖之星
日期:2016-05-08 06:20:00IT运维版块每日发帖之星
日期:2016-05-13 06:20:00IT运维版块每日发帖之星
日期:2016-05-28 06:20:00每日论坛发贴之星
日期:2016-05-28 06:20:00
10 [报告]
发表于 2009-11-11 13:13 |只看该作者
dreamice兄又出好文章了。拜读一把。
您需要登录后才可以回帖 登录 | 注册

本版积分规则 发表回复

  

北京盛拓优讯信息技术有限公司. 版权所有 京ICP备16024965号-6 北京市公安局海淀分局网监中心备案编号:11010802020122 niuxiaotong@pcpop.com 17352615567
未成年举报专区
中国互联网协会会员  联系我们:huangweiwei@itpub.net
感谢所有关心和支持过ChinaUnix的朋友们 转载本站内容请注明原作者名及出处

清除 Cookies - ChinaUnix - Archiver - WAP - TOP