linux内核信号量和互斥锁使用教程
信号量概念
Linux 内核的信号量在概念和原理上与用户态的 System V 的 IPC 机制信号量是一样的,但是它绝不可能在内核之外使用,因此它与 System V 的 IPC 机制信号量毫不相干。
信号量在创建时需要设置一个初始值,表示同时可以有几个任务可以访问该信号量保护的共享资源,初始值为 1 就变成互斥锁(Mutex),即同时只能有一个任务可以访问信号量保护的共享资源。
一个任务要想访问共享资源,首先必须得到信号量,获取信号量的操作将把信号量的值减 1,若当前信号量的值为负数,表明无法获得信号量,该任务必须挂起在该信号量的等待队列等待该信号量可用;若当前信号量的值为非负数,表示可以获得信号量,因而可以立刻访问被该信号量保护的共享资源。
当任务访问完被信号量保护的共享资源后,必须释放信号量,释放信号量通过把信号量的值加 1 实现,如果信号量的值为非正数,表明有任务等待当前信号量,因此它也唤醒所有等待该信号量的任务。
内核信号量核心结构
内核中使用 struct semaphore 结构来描述一个信号量,结构定义如下:
<pre class="has">
struct semaphore {
raw_spinlock_t lock;
unsigned int count; //信号值,只要这个值为正数,则信号可用, 一般情况设置 0 或 1。
struct list_head wait_list;
};
### 信号量相关 API
DEFINE\_SEMAPHORE (name)
宏原型\#define DEFINE\_SEMAPHORE(name) \\
struct semaphore name = \_\_SEMAPHORE\_INITIALIZER(name, 1)宏功能该宏声明一个信号量 name 并初始化它的值为 1,即声明一个互斥锁宏参数name: 要定义的信号量变量名所在头文件include\\linux\\semaphore.h宏定义文件include\\linux\\semaphore.h备注Linux3.5 内核出现 在 linux 2.6 中名字为 DECLARE\_MUTEX(name)
void sema\_init(struct semaphore \*sem, int val)
宏(函数) 原型
void sema\_init(struct semaphore \*sem, int val)
宏功能该函数用于初始化一个互斥锁,即它把信号量 sem 的值设置为 1。宏参数sem: 要初始化的信号量的指针所在头文件include\\linux\\semaphore.h宏定义文件include\\linux\\semaphore.h备注
void sema\_init (struct semaphore \*sem, int val);
宏原型void sema\_init (struct semaphore \*sem, int val);宏功能初始化设置信号量的初值,它设置信号量 sem 的值为 val宏参数sem: 要初始化的信号量的指针;
val: 信号量的值所在头文件include\\linux\\semaphore.h宏定义文件include\\linux\\semaphore.h
定义、初始化信号量相关 API:这是一个静态定义方式。 定义一个名字为 name,信号量值为 1 的信号量结构变量。
示例:
struct semaphore sem;
sema\_init(&sem, 1);
以上两行等效:
DEFINE\_SEMAPHORE(sem);
void down(struct semaphore \*sem)
函数原型void down(struct semaphore \* sem);函数功能用于获得信号量 sem,它会导致睡眠,因此不能在中断上下文(包括 IRQ 上下文和 softirq 上
下文)使用该函数。该函数将把 sem 的值减 1,如果信号量 sem 的值非负,就直接返回,否
则调用者将被挂起,直到别的任务释放该信号量才能继续运行。函数参数sem: 要初始化获取的信号量的指针;函数返回值无所在头文件include\\linux\\semaphore.h函数定义文件kernel\\semaphore.c
int down\_timeout(struct semaphore \*sem, long jiffies);
sem: 信号量结构指针
jiffies: 要设置超时时间,单位是时钟节拍。
阻塞地请求一个信号量,如果信号量大于 0, 则可以马上返回, 否则休眠,直到有其他进程释放信号量
(把信号量的 count 修改为大于 0 的值) 或超时时间到。
这个函数效果和 down 函数很像,它也是不可中断的休眠。
int down\_interruptible(struct semaphore \* sem);
函数原型int down\_interruptible(struct semaphore \* sem);函数功能该函数功能与 down 类似,不同之处为, down 不会被信号(signal)打断,但 down\_interruptible
能被信号打断,因此该函数有返回值来区分是正常返回还是被信号中断,如果返回 0,表示
获得信号量正常返回,如果被信号打断,返回-EINTR。函数参数sem: 要获取的信号量的指针;函数返回值0:得到信号量正常返回 ; 负数:被信号中断返回所在头文件include\\linux\\semaphore.h函数定义文件kernel\\semaphore.c
int down\_trylock(struct semaphore \* sem);
函数原型int down\_trylock(struct semaphore \* sem);函数功能该函数试着获得信号量 sem,如果能够立刻获得,它就获得该信号量并返回 0,否则,表示
不能获得信号量 sem,返回值为非 0 值。因此,它不会导致调用者睡眠,可以在中断上下文
使用。函数参数sem: 要获取的信号量的指针;函数返回值0:得到信号量正常返回 ; 非 0:得不到信号量所在头文件include\\linux\\semaphore.h函数定义文件kernel\\semaphore.c
void up(struct semaphore \* sem);
函数原型void up(struct semaphore \* sem);函数功能该函数释放信号量 sem,即把 sem 的值加 1,如果 sem 的值为非正数,表明有任务等待该信
号量,因此唤醒这些等待者。函数参数sem: 要初释放的信号量的指针;函数返回值无头文件include\\linux\\semaphore.h函数定义文件kernel\\semaphore.c
虽然信号量可以设置为大于 1 的值,但是信号量在绝大部分情况下作为互斥锁使用。
- - - - - -
简单信号量使用例子实现设备只能被一个进程打开:
```cpp #include/* Needed by all modules */ #include /* Needed for the module-macros */ #include #include #include #define LEDS_MAJOR 255 #define DEVICE_NAME "miscdev_semaphore" DECLARE_MUTEX(lock); //定义一个互斥信号量,并初始化 1; static int first_miscdev_open(struct inode *pinode, struct file *pfile) { printk (KERN_EMERG "Linux miscdevice:%s is call\r\n",__FUNCTION__); /* 获取信号量 */ if (!down_trylock(&lock)) return 0; else return -EBUSY; } int first_miscdev_release (struct inode *inode, struct file *file) { printk (KERN_EMERG "Linux miscdevice:%s is call\r\n",__FUNCTION__); up(&lock); //释放信号量 return 0; } static struct file_operations dev_fops = { .owner = THIS_MODULE, .open = first_miscdev_open, .release= first_miscdev_release, }; static struct miscdevice misc = { .minor = LEDS_MAJOR, .name = DEVICE_NAME, .fops = &dev_fops, }; /* 模块装载执行函数 */ static int __init first_miscdev_init(void) { int ret; ret = misc_register(&misc); //注册混杂设备 if(ret <0) printk (KERN_EMERG DEVICE_NAME"\t err\r\n"); printk (KERN_EMERG DEVICE_NAME"\tinitialized\n"); return ret; } /* 模块卸载执行函数 */ static void __exit first_miscdev_exit(void) { misc_deregister(&misc); printk(KERN_EMERG "Goodbye,cruel world!, priority = 0\n"); } module_init(first_miscdev_init); module_exit(first_miscdev_exit); MODULE_LICENSE("GPL"); MODULE_AUTHOR("XYD"); MODULE_DESCRIPTION("This the samlpe drv_test"); ``` - - - - - - 互斥信号量 ----- ### 互斥信号量概念: 互斥锁主要用于实现内核中的互斥访问功能。内核互斥锁是在原子 API 之上实现的,但这对于内核用户是不可见的。 在大部分场合中,对共享资源的访问,都是使用独占方式, 在这种情况下, 提供专门的互斥接口给编程者使用。 对它的访问必须遵循一些规则:同一时间只能有一个任务持有互斥锁,而且只有这个任务可以对互斥锁进行解锁。互斥锁不能进行递归锁定或解锁。一个互斥锁对象必须通过其 API 初始化,而不能使用 memset 或复制初始化。一个任务在持有互斥锁的时候是不能结束的。互斥锁所使用的内存区域是不能被释放的。使用中的互斥锁是不能被重新初始化的。并且互斥锁不能用于中断上下文。但是互斥锁比当前的内核信号量选项更快,并且更加紧凑,因此如果它们满足您的需求,那么它们将是您明智的选择。 ### 内核信号量核心结构 内核中使用 struct mutex 结构来描述一个信号量,结构定义如下: ``` ```cpp struct mutex { /* 1: unlocked, 0: locked, negative: locked, possible waiters */ atomic_t count; spinlock_t wait_lock; struct list_head wait_list; #if defined(CONFIG_DEBUG_MUTEXES) || defined(CONFIG_SMP) struct task_struct *owner; #endif #ifdef CONFIG_DEBUG_MUTEXES const char *name; void *magic; #endif #ifdef CONFIG_DEBUG_LOCK_ALLOC struct lockdep_map dep_map; #endif }; ``` 关键成员说明: atomic\_t count: 指示互斥锁的状态: 1--没有上锁,可以获得 0--被锁定,不能获得 负数--被锁定,且可能在该锁上有等待进程 初始化时为没有上锁状态。 spinlock\_t wait\_lock: 等待获取互斥锁中使用的自旋锁。在获取互斥锁的过程中,操作会在自旋锁的保护中进行。初始化为为 锁定, 用户一般无需关心。 struct list\_head wait\_list: 等待互斥锁的进程队列, 用户无需关心。 ### 互斥信号量相关 API ### DEFINE\_MUTEX(mutexname) 宏原型\#define DEFINE\_MUTEX(mutexname) \\ struct mutex mutexname = \_\_MUTEX\_INITIALIZER(mutexname)宏功能该宏声明一个互斥信号量 name 并初始化它宏参数name: 要定义的互斥信号量变量名所在头文件include\\linux\\mutex.h备注linux3.5出现 ### mutex\_init(mutex) 宏原型\# define mutex\_init(mutex) \\ do { \\ static struct lock\_class\_key \_\_key; \\ \_\_mutex\_init((mutex), #mutex, &\_\_key); \\ } while (0)宏功能该宏初始化一个已经定义的互斥信号量, metex 传递的是指针宏参数mutex: 初始化的的互斥信号量指针所在头文件include\\linux\\mutex.h宏定义文件include\\linux\\mutex.h ### int mutex\_is\_locked(struct mutex \*lock) 宏原型int mutex\_is\_locked(struct mutex \*lock)宏功能该函数检测互斥锁是否已经被锁定,宏参数lock: 互斥锁变量是指针返回值1 : 已经锁定 0: 没有锁定所在头文件include\\linux\\mutex.h ### mutex\_lock(lock) 或 void mutex\_lock(struct mutex \*lock); 宏(函数) 原型mutex\_lock(lock) 或 void mutex\_lock(struct mutex \*lock);宏功能该宏(函数) 获取互斥锁是否, 如没有得到会休眠,直接得到为止宏参数lock: 互斥锁变量是指针返回值无所在头文件include\\linux\\mutex.h宏定义文件include\\linux\\mutex.h备注根据内核配置不同,该功能有有宏版本和函数版本 ### mutex\_lock\_interruptible(lock) 或 int mutex\_lock\_interruptible(struct mutex \*lock); 宏原型\#define mutex\_lock\_interruptible(lock) mutex\_lock\_interruptible\_nested(lock, 0) 或 int \_\_must\_check mutex\_lock\_interruptible(struct mutex \*lock);宏功能该宏(函数)获取互斥锁是否, 如没有得到会休眠,直接得到为止, 但是可以被信号中断宏参数lock: 互斥锁变量是指针返回值成功获得锁返回 0, 被信号中断返回-EINIR所在头文件include\\linux\\mutex.h宏定义文件include\\linux\\mutex.h备注根据内核配置不同,该功能有有宏版本和函数版本 ### int mutex\_trylock(struct mutex \*lock); 宏原型int mutex\_trylock(struct mutex \*lock);宏功能该函数是获取互斥锁, 如没有得到不会休眠,马上返回宏参数lock: 互斥锁变量是指针返回值无所在头文件include\\linux\\mutex.h宏定义文件include\\linux\\mutex.c备注根据内核配置不同,该功能有有宏版本和函数版本 ### void mutex\_unlock(struct mutex \*lock); 宏原型void mutex\_unlock(struct mutex \*lock);宏功能该函数是释放互斥锁宏参数lock: 互斥锁变量是指针返回值无所在头文件include\\linux\\mutex.h宏定义文件include\\linux\\mutex.c ### 互斥锁应用例子: ``````cpp /* chardev.c */ #include/* Needed by all modules */ #include /* Needed for the module-macros */ #include #include #include /* 自动创建设备文件 */ #include #include /*ioremap*/ #include /*copy_from_user ,copy_to_user*/ #include #include #include #include #include /* s3c_gpio_cfgpin S3C_GPIO_OUTPUT */ static struct cdev *pcdev; static struct device *this_device = NULL; static struct class *leds_class = NULL; /* 是否开启 gpio_request 函数功能 */ #define GPIO_REQUEST 1 //0 :关,非0 :开 static int led_gpios[] = { EXYNOS4X12_GPM4(0), EXYNOS4X12_GPM4(1), EXYNOS4X12_GPM4(2), EXYNOS4X12_GPM4(3), }; #define LED_NUM ARRAY_SIZE(led_gpios) #define DEV_NAME "myleds" unsigned int major = 0; //主设备号 unsigned int minor = 0; //次设备号 unsigned int devnr = 0; //设备号 char *chrdev_name = DEV_NAME; //设备名 module_param(major, int, S_IRUGO | S_IWUSR); module_param(minor, int, S_IRUGO | S_IWUSR); module_param(chrdev_name, charp, S_IRUGO | S_IWUSR); struct mutex lock; //打开设备时候执行的程序 static int chrdev_open(struct inode *pinode, struct file *pfile) { mutex_lock(&lock); printk(KERN_EMERG "line:%d,%s is call\n", __LINE__, __FUNCTION__); return 0; } //关闭设备时执行的程序 static int chrdev_release(struct inode *pinode, struct file *pfile) { mutex_unlock(&lock); printk(KERN_EMERG "line:%d,%s is call\n", __LINE__, __FUNCTION__); return 0; } //读接口函数 static ssize_t chrdev_read ( struct file *file, char __user *buf, size_t count, loff_t * f_pos ) { char led_buffer[4] = {2, 2, 2, 2}; int i = 0; // printk ( KERN_EMERG "line:%d,%s is call\n", __LINE__, __FUNCTION__ ); /* count ==0 ,则返回0,不是错误 */ if ( !count ) { return 0; } /* 当前已经到文件末尾,不能再写,返回0,*/ if ( count > LED_NUM ) { count = LED_NUM; } /* 准备数据,0表示灭,1表示亮 */ for (i = 0; i < LED_NUM; i++) { led_buffer[i] = !gpio_get_value(led_gpios[i]); } if ( copy_to_user ( buf, &led_buffer[0], count ) ) { return -EFAULT; } return count; } //写接口函数 static ssize_t chrdev_write(struct file *pfile, const char __user *user_buf, size_t count, loff_t *off) { int ret = 0; char buf[LED_NUM] = {0}; int i = 0; // printk(KERN_EMERG "line:%d,%s is call\n", __LINE__, __FUNCTION__); //应用程序传递count=0下来,并不是错误,应该返回0 if(count == 0) { return 0; } //因为板子只有4个灯,所以防止用户程序恶意破坏系统。 if(count > LED_NUM) { count = LED_NUM; } //把用户空间传递下来的数据复制到内核空间的buf数组中 ret = copy_from_user(buf, user_buf, count); //返回,成功是0,其他是失败 if(ret) { //如果复制失败返回-1; return -EFAULT; } for(i = 0; i < count; i++) { if(buf[i] == 1) // GPM4DAT &= ~(1 << (0 + i));/* 亮 */ { gpio_set_value(led_gpios[i], 0); } else if(buf[i] == 0) // rGPM4DAT |= (1 << (0 + i));/* 灭 */ { gpio_set_value(led_gpios[i], 1); } } count = 1; return count; } //文件操作方法: static const struct file_operations chrdev_fops = { .read = chrdev_read, .write = chrdev_write, .release = chrdev_release, .open = chrdev_open, }; static int __init chrdev_init(void) { int ret = 0; int i; /* IO口配置代码 */ for (i = 0; i < LED_NUM; i++) { #if GPIO_REQUEST ret = gpio_request(led_gpios[i], "LED"); if (ret) { printk("%s: request GPIO %d for LED failed, ret = %d\n", chrdev_name, led_gpios[i], ret); goto gpio_request_err; } #endif //s3c_gpio_cfgpin(led_gpios[i], S3C_GPIO_OUTPUT); //gpio_set_value(led_gpios[i], 1); //gpio_direction_output等效于上面两条语句, gpio_direction_output(led_gpios[i], 1); } //分配cdev核心结构; pcdev = cdev_alloc(); if (pcdev == NULL) { ret = -ENOMEM; printk(KERN_EMERG"cdev_alloc_err error\n"); goto cdev_alloc_err; } //如果主设备号是0,则动态分配设备号 if(!major) { ret = alloc_chrdev_region(&devnr, minor, 1, chrdev_name); if(ret < 0) { printk(KERN_EMERG"alloc_chrdev_region error\n"); goto devnr_requst_err; } } else { //合成设备号 devnr = MKDEV(major, minor); //静态注册设备号 ret = register_chrdev_region(devnr, 1, chrdev_name); if(ret != 0) { printk(KERN_EMERG"register_chrdev_region error\n"); goto devnr_requst_err; } } //cdev结构体初始化 cdev_init(pcdev, &chrdev_fops); //注册字符设备 ret = cdev_add(pcdev, devnr, 1); if ( ret < 0) { printk(KERN_EMERG"cdev_add error\n"); goto cdev_add_err; } //输出字符设备主设备号和次设备号 printk(KERN_EMERG "name:%s,major:%d,minor:%d\n", chrdev_name, MAJOR(devnr), MINOR(devnr)); //创建一个类 leds_class = class_create(THIS_MODULE, "leds_class"); if ( IS_ERR(leds_class) ) { ret = PTR_ERR(leds_class); printk(KERN_EMERG"class_create error\n"); goto class_create_err; } //创建一个设备 this_device = device_create(leds_class, NULL, devnr, NULL, "%s", chrdev_name); if ( IS_ERR(this_device) ) { ret = PTR_ERR(this_device); printk(KERN_EMERG"this_device error\n"); goto device_create_err; } mutex_init(&lock); return 0; device_create_err: class_destroy(leds_class); class_create_err: unregister_chrdev(major, chrdev_name); cdev_add_err: devnr_requst_err: if (pcdev) { cdev_del(pcdev); } unregister_chrdev_region(devnr, 1); cdev_alloc_err: #if GPIO_REQUEST gpio_request_err: /* gpio 反向释放 */ for ( --i ; i >= 0 ; i-- ) { gpio_free(led_gpios[i]); } #endif return ret; } static void __exit chrdev_exit(void) { int i = 0; device_destroy(leds_class, devnr); class_destroy(leds_class); /* gpio 反向释放 */ for ( ; i < 4 ; i++) { gpio_free(led_gpios[i]); } cdev_del(pcdev); unregister_chrdev_region(devnr, 1); printk(KERN_EMERG "Goodbye,chrdev\n"); } module_init(chrdev_init); module_exit(chrdev_exit); MODULE_LICENSE("Dual BSD/GPL"); MODULE_AUTHOR("learn"); //optional MODULE_DESCRIPTION("STUDY_MODULE"); //optional ``` ###