信号量(semaphore)是用于保护临界区的一种常用方法,它的使用方式和自旋锁类似。与自旋锁相同,只有得到信号量的进程才能执行临界区代码。但是,与自旋锁不同的是,当获取不到信号量时,进程不会原地打转而是进入休眠等待状态。 信号量的相关操作如下: 一:定义信号量 二:初始化信号量 - void sema_init (struct semaphore *sem, int val);
复制代码该函数初始化信号量,并设置信号量sem的值为val。尽管信号量可以被初始化为大于1的值从而成为一个计数信号量,但是它通常不被这样使用。 - void init _ MUTEX(struct semaphore *sem);
复制代码该函数用于初始化一个用于互斥的信号量,它把信号量sem的值设置为1,等同于: - sema_init (struct semaphore *sem, 1)。
复制代码以下函数也用于初始化一个信号量,但它把信号量sem的值设置为0: - void init _ MUTEX _ LOCKED (struct semaphore *sem);
复制代码它等同于: - sema_init (struct semaphore *sem, 0)。
复制代码此外,下面宏是定义并初始化信号量的“快捷方式”。 三:获得信号量 - void down(struct semaphore * sem);
复制代码该函数用于获得信号量sem,它会导致睡眠,因此不能在中断上下文使用。 - int down_interruptible(struct semaphore * sem);
复制代码该函数功能与down()类似,不同之处为,因为down()而进入睡眠状态的进程不能被信号打断,而因为down_interruptible()而进入睡眠状态的进程能被信号打断,信号也会导致该函数返回,这时候函数的返回值非0。 - int down_trylock(struct semaphore * sem);
复制代码该函数尝试获得信号量sem,如果能够立刻获得,它就获得该信号量并返回0,否则,返回非0值。它不会导致调用者睡眠,可以在中断上下文使用。 在使用down_interruptible()获取信号量时,对返回值一般会进行检查,如果非0,通常立即返回-ERESTARTSYS,如: - if (down _ interruptible(&sem))
- {
- return - ERESTARTSYS;
- }
复制代码四:释放信号量 - void up(struct semaphore * sem);
复制代码该函数释放信号量sem,唤醒等待者。 信号量一般这样被使用,如下所示: - //定义信号量
- DECLARE _ MUTEX(mount _ sem);
- down(&mount _ sem);//获取信号量,保护临界区
- ...
- critical section //临界区
- ...
- up(&mount _ sem);//释放信号量
复制代码 编写一个驱动,用信号量的方式保证驱动只能被打开一次,当多个进程打开该驱动时,提示错误信息,并返回失败。 在kernel/drivers/char/x4412目录下新建x4412-semaphore.c文件,编辑内容如下: - #include <linux/module.h>
- #include <linux/fs.h>
- #include <linux/errno.h>
- #include <linux/init.h>
- #include <linux/cdev.h>
- #include <asm/uaccess.h>
- #include <linux/device.h>
-
- static int x4412_semaphore_major;
- static struct cdev x4412_semaphore;
- static struct class *cdev_class;
- DEFINE_SEMAPHORE(sem_lock);
-
- int x4412_semaphore_open(struct inode *inode, struct file *filp)
- {
- int ret;
- if(down_trylock(&sem_lock))
- {
- printk("x4412-semaphore open error.\r\n");
- return -EBUSY;
- }
- return 0;
- }
- int x4412_semaphore_release(struct inode *inode, struct file *filp)
- {
- printk("x4412-semaphore close.\r\n");
- up(&sem_lock);
- return 0;
- }
- static const struct file_operations x4412_fops =
- {
- .owner = THIS_MODULE,
- .open = x4412_semaphore_open,
- .release = x4412_semaphore_release,
- };
- static void x4412_setup_cdev(void)
- {
- int err, devno = MKDEV(x4412_semaphore_major, 0);
- cdev_init(&x4412_semaphore, &x4412_fops);
- err = cdev_add(&x4412_semaphore, devno, 1);
- if (err)
- printk(KERN_NOTICE "Error %d adding x4412_semaphore", err);
- }
- static void x4412_clear_cdev(void)
- {
- cdev_del(&x4412_semaphore);
- unregister_chrdev_region(MKDEV(x4412_semaphore_major, 0), 1);
- }
- int x4412_semaphore_init(void)
- {
- int result;
- dev_t devno;
- result = alloc_chrdev_region(&devno, 0, 1, "x4412-semaphore");
- x4412_semaphore_major = MAJOR(devno);
- if(result < 0)
- {
- printk("register x4412-semaphore error!\r\n");
- return result;
- }
- x4412_setup_cdev();
- cdev_class = class_create(THIS_MODULE,"x4412-semaphore");
- if(IS_ERR(cdev_class))
- {
- x4412_clear_cdev();
- return PTR_ERR(cdev_class);
- }
- device_create(cdev_class,NULL,MKDEV(x4412_semaphore_major,0),NULL,"x4412-semaphore");
- return 0;
- }
- void x4412_semaphore_exit(void)
- {
- device_destroy(cdev_class,MKDEV(x4412_semaphore_major,0));
- class_destroy(cdev_class);
- x4412_clear_cdev();
- }
- MODULE_AUTHOR("www.9tripod.com");
- MODULE_LICENSE("GPL");
- module_init(x4412_semaphore_init);
- module_exit(x4412_semaphore_exit);
复制代码 驱动在open函数中通过down_trylock函数尝试获取信号量,若获取成功,函数将返回0,对应驱动打开成功,若获取失败,函数将返回失败,并打印出错提示信息。release函数通过up函数释放信号量,释放之后,其他进程将允许访问本驱动了。 编辑kernel/drivers/char/x4412/Kconfig文件,添加如下语句: - config X4412_SEMAPHORE_DRIVER
- tristate "x4412 semaphore driver"
- default m
- help
- compile for x4412 semaphore driver,y for kernel,m for module.
复制代码 编辑kernel/drivers/char/x4412/Makefile文件,添加如下语句: - obj-$(CONFIG_X4412_SEMAPHORE_DRIVER) += x4412-semaphore.o
复制代码 编译内核,在kernel/drivers/char/x4412目录将会生成目标文件x4412-semaphore.ko。 在ubuntu用户目录或samba目录下新建x4412-semaphore-app.c文件,编辑内容如下: - #include <stdio.h>
- #include <stdlib.h>
- #include <fcntl.h>
- #define DEVICE_NAME "/dev/x4412-semaphore"
- #define SEM_DEBUG
-
- int main(int argc,char **argv)
- {
- int fd;
- fd = open(DEVICE_NAME,O_RDWR);
- if(fd < 0)
- {
- printf("open device %s error \n",DEVICE_NAME);
- }
- else
- {
- #ifdef SEM_DEBUG
- fd = open(DEVICE_NAME,O_RDWR);
- if(fd < 0)
- {
- printf("repeat open device %s error!\r\n",DEVICE_NAME);
- }
- usleep(5000);
- #endif
- close(fd);
- }
- return 0;
- }
复制代码 该测试程序两次调用open函数打开驱动,模拟多个进程同时调用一个驱动的情况。使用如下指令编译测试应用程序: - arm-none-linux-gnueabi-gcc x4412-semaphore-app.c -o x4412-semaphore-app
复制代码 这时在当前目录下将会生成目标映像文件x4412-semaphore-app。将驱动模块和应用映像拷贝到开发板,加载模块测试,观察是否和期望的效果一样。 - [root@x4412 mnt]# insmod x4412-semaphore.ko
- [root@x4412 mnt]# ./x4412-semaphore-app
- [ 1385.583258] x4412-semaphore open error.
- repeat open device /dev/x4412-semaphore error!
- [ 1385.591150] x4412-semaphore close.
复制代码 从前面的示例可以看出,自旋锁和信号量都是解决互斥问题的基本手段,面对特定的情况,应该如何进行选择呢?选择的依据是临界区的性质和系统的特点。从严格意义上说,信号量和自旋锁属于不同层次的互斥手段,前者的实现依赖于后者。在信号量本身的实现上,为了保证信号量结构存取的原子性,在多CPU 中需要自旋锁来互斥。 信号量是进程级的,用于多个进程之间对资源的互斥,虽然也是在内核中,但是该内核执行路径是以进程的身份,代表进程来争夺资源的。如果竞争失败,会发生进程上下文切换,当前进程进入睡眠状态,CPU 将运行其他进程。鉴于进程上下文切换的开销也很大,因此,只有当进程占用资源时间较长时,用信号量才是较好的选择。 当所要保护的临界区访问时间比较短时,用自旋锁是非常方便的,因为它节省上下文切换的时间。但是 CPU 得不到自旋锁会在那里空转直到其他执行单元解锁为止,所以要求锁不能在临界区里长时间停留,否则会降低系统的效率。由此,可以总结出自旋锁和信号量选用的 3 项原则。 l 当锁不能被获取时,使用信号量的开销是进程上下文切换时间Tsw,使用自旋锁的开销是等待获取自旋锁(由临界区执行时间决定)Tcs,若Tcs比较小,应使用自旋锁,若Tcs很大,应使用信号量。 l 信号量所保护的临界区可包含可能引起阻塞的代码,而自旋锁则绝对要避免用来保护包含这样代码的临界区。因为阻塞意味着要进行进程的切换,如果进程被切换出去后,另一个进程企图获取本自旋锁,死锁就会发生。 l 信号量存在于进程上下文,因此,如果被保护的共享资源需要在中断或软中断情况下使用,则在信号量和自旋锁之间只能选择自旋锁。当然,如果一定要使用信号量,则只能通过 down_trylock()方式进行,不能获取就立即返回以避免阻塞。
在x4412开发板和ibox卡片电脑上直接能用的映像文件:
x4412-semaphore.ko
(3.82 KB, 下载次数: 7)
x4412-semaphore-app
(6.07 KB, 下载次数: 8)
|