T11 驱动并发
1 为什么需要并发控制
以T10案例代码为例,我们在内核中申请了一片缓冲区,假设此刻有2个及以上进程同时来访问这个内核驱动,而且二者同时执行到了下述代码区,那么此刻内核将会拷贝2个用户空间的数据到一个缓冲区,假设进程A刚好拷贝了一半数据到内核buffer后进程B也拷贝部分数据给内核buffer,那么将会导致内核中的buffer内数据变得不可控。
ret = copy_from_user(kbuf, buf, write_bytes);
上述过程其实就是属于一种竞态(race condition),竞态会导致对共享内存的非控制访问,最终可能导致内存泄漏甚至系统崩溃等灾难性的结果。
2 避免方法
- 只要可能,就应该避免资源的共享。如果没有并发的访问,也就不会产生竞态。因此编写的内核代码应该具有最少的共享,即避免使用全局变量。
- 使用“锁定”或者“互斥”,即保证某一进程在使用共享资源的时候其他进程不能使用。
3 原子操作
- 原子的操作指的是进程在执行过程中不会被别的代码所中断的操作
- 在linux中原子操作的方法有很多,有整型原子与位原子,他们在任何场景下操作都是原子的,且这些原子操作的实现都是依赖CPU来实现的,因此这些原子操作的API都是与CPU的架构密切相关
3.1 原子操作API
- ARM架构,位于
/kernel/arch/arm/include/asm/atomic.h
下
/*
功能:设置原子的值
*/
static inline void atomic_set(atomic_t *v, int i);
/* 定义原子变量并初始化为0 */
atomic_t value = ATOMIC_INIT(0);
/* 获取原子变量的值 */
#define atomic_read(v) ((v)->counter)
/* 原子变量加减 */
#define atomic_add(i, v) (void) atomic_add_return(i, v)
#define atomic_inc(v) (void) atomic_add_return(1, v)
#define atomic_sub(i, v) (void) atomic_sub_return(i, v)
#define atomic_dec(v) (void) atomic_sub_return(1, v)
3.2 原子操作示例
- 驱动代码,我们初始化atomic原子变量为1,当第一个调用驱动的用户代码调用open函数的时候会将atomic变量减1,此刻为0,此刻表明驱动正在被调用;那么此刻当第二个用户代码调用驱动的open的时候会继续将atomic变量减1,为-1,此刻
atomic_dec_and_test
函数会返回0,仍表示驱动被占用,那么驱动将会返回一个忙的信号
#include <linux/init.h>
#include <linux/module.h>
#include <linux/cdev.h>
#include <linux/fs.h>
#include <linux/device.h>
#include <asm/uaccess.h>
#include <linux/slab.h>
#include <linux/miscdevice.h>
#include <asm/atomic.h>
#include "cdev_test_v4.h"
#define BUF_SIZR 1024
#define MAJOR_NUM 168
#define MINOR_NUM 0
struct demo_cdev {
char *buffer; //my private memory
int value;
struct miscdevice *mdev;
};
/* step1: malloc memory for char device */
struct demo_cdev *demo_dev;
static atomic_t demo_available = ATOMIC_INIT(1);
static ssize_t demo_read(struct file *file, char __user *buf, size_t size, loff_t *pos)
{
int ret = -1;
int read_bytes;
struct demo_cdev *demo = file->private_data;
char *kbuf = demo->buffer + *pos;
printk(KERN_INFO "Enter: %s\n", __func__);
/* determine whether user has finished reading data */
if (*pos >= BUF_SIZR) {
return 0;
}
if (size > (BUF_SIZR - *pos)) {
read_bytes = BUF_SIZR - *pos;
} else {
read_bytes = size;
}
ret = copy_to_user(buf, kbuf, read_bytes);
if (ret != 0) {
return -EFAULT;
}
*pos += read_bytes;
return read_bytes;
}
static ssize_t demo_write(struct file *file, const char __user *buf, size_t size, loff_t *pos)
{
int ret;
int write_bytes;
struct demo_cdev *demo = file->private_data;
char *kbuf = demo->buffer + *pos;
printk(KERN_INFO "Enter: %s\n", __func__);
if (*pos >= BUF_SIZR) {
return 0;
}
if (size > (BUF_SIZR - *pos)) {
write_bytes = BUF_SIZR - *pos;
} else {
write_bytes = size;
}
ret = copy_from_user(kbuf, buf, write_bytes);
if (ret != 0) {
return -EFAULT;
}
*pos += write_bytes;
return write_bytes;
}
static int demo_open(struct inode *node, struct file *file)
{
printk(KERN_INFO "Enter: %s\n", __func__);
if (!atomic_dec_and_test(&demo_available)) {
printk(KERN_ERR "file already open\n");
/* 别忘了将信号量加回来 */
atomic_inc(&demo_available);
return -EBUSY;
}
file->private_data = (void *)demo_dev;
return 0;
}
static int demo_release(struct inode *node, struct file *file)
{
printk(KERN_INFO "Enter: %s\n", __func__);
atomic_inc(&demo_available);
return 0;
}
static long demo_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
{
int ret = 0;
struct demo_cdev *demo = file->private_data;
switch(cmd) {
case CDEV_TEST_V4_CLEAN:
memset(demo->buffer, 0x00, BUF_SIZR);
printk(KERN_INFO "cmd: clean\n");
break;
case CDEV_TEST_V4_GETVAL:
put_user(demo->value, (int *)arg);
printk(KERN_INFO "cmd: getval\n");
break;
case CDEV_TEST_V4_SETVAL:
demo->value = (int)arg;
printk(KERN_INFO "cmd: setval\n");
break;
default:
break;
}
return (long)ret;
}
static struct file_operations demo_operation= {
.open = demo_open,
.release = demo_release,
.read = demo_read,
.write = demo_write,
.unlocked_ioctl = demo_ioctl,
};
static struct miscdevice misc_struct = {
.minor = MISC_DYNAMIC_MINOR,
.name = "misc_dev",
.fops = &demo_operation,
};
static int __init demo_init(void)
{
int ret = -1;
printk(KERN_INFO "Enter: %s\n", __func__);
demo_dev = (struct demo_cdev *)kmalloc(sizeof(struct demo_cdev), GFP_KERNEL);
if (!demo_dev) {
printk(KERN_ERR "failed to malloc demo_dev\n");
ret = -ENOMEM;
goto ERROR_MALLOC_DEMODEV;
}
demo_dev->value = 1;
demo_dev->buffer = (char *)kmalloc(BUF_SIZR, GFP_KERNEL);
if (!demo_dev->buffer) {
printk(KERN_ERR "malloc %d bytes failed\n", BUF_SIZR);
ret = -ENOMEM;
goto ERROR_MALLOC_BUFFER;
}
memset(demo_dev->buffer, 0x00, BUF_SIZR);
demo_dev->mdev = &misc_struct;
ret = misc_register(demo_dev->mdev);
if (ret < 0) {
printk(KERN_ERR "failed to register misc\n");
goto ERROR_MISC;
}
printk(KERN_INFO "demo char device init done\n");
return 0;
ERROR_MISC:
kfree(demo_dev->buffer);
demo_dev->buffer = NULL;
ERROR_MALLOC_BUFFER:
kfree(demo_dev);
demo_dev = NULL;
ERROR_MALLOC_DEMODEV:
return ret;
}
static void __exit demo_exit(void)
{
printk(KERN_INFO "Enter: %s\n", __func__);
misc_deregister(demo_dev->mdev);
kfree(demo_dev->buffer);
demo_dev->buffer = NULL;
kfree(demo_dev);
demo_dev = NULL;
printk(KERN_INFO "demo char device exit done\n");
}
module_init(demo_init);
module_exit(demo_exit);
MODULE_AUTHOR("Qi Han");
MODULE_LICENSE("GPL");
- 测试代码,我们先打开一个文件,之后让第一个进程睡眠10秒钟,即进程占用内核10秒钟,之后在第一个进程没有退出之前其他进程都不可调用内核的open函数。
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/ioctl.h>
#include <fcntl.h>
#include "cdev_test_v4.h"
#define DEVICE_PATH "/dev/misc_dev"
int main(void)
{
int fd;
int ret;
printf("opening...\n");
fd = open(DEVICE_PATH, O_RDWR);
if (fd < 0) {
printf("failed to open\n");
return -1;
}
printf("sleeping...\n");
sleep(7);
printf("closing\n");
close(fd);
return 0;
}
- 调试
hq@hq-virtual-machine:~/桌面/linux-5.8/drivers/study_driver_hq/cdev_test$ sudo insmod atomic.ko
[sudo] hq 的密码:
hq@hq-virtual-machine:~/桌面/linux-5.8/drivers/study_driver_hq/cdev_test$ sudo chmod 666 /dev/misc_dev
hq@hq-virtual-machine:~/桌面/linux-5.8/drivers/study_driver_hq/cdev_test$ ./test
opening...
sleeping...
closing
hq@hq-virtual-machine:~/桌面/linux-5.8/drivers/study_driver_hq/cdev_test$ ./test &
[1] 7064
hq@hq-virtual-machine:~/桌面/linux-5.8/drivers/study_driver_hq/cdev_test$ opening...
sleeping...
hq@hq-virtual-machine:~/桌面/linux-5.8/drivers/study_driver_hq/cdev_test$ ./test
opening...
failed to open
hq@hq-virtual-machine:~/桌面/linux-5.8/drivers/study_driver_hq/cdev_test$ ./test
opening...
failed to open
hq@hq-virtual-machine:~/桌面/linux-5.8/drivers/study_driver_hq/cdev_test$ ./test
opening...
failed to open
hq@hq-virtual-machine:~/桌面/linux-5.8/drivers/study_driver_hq/cdev_test$ ./test
opening...
failed to open
hq@hq-virtual-machine:~/桌面/linux-5.8/drivers/study_driver_hq/cdev_test$ closing
[1]+ 已完成 ./test
hq@hq-virtual-machine:~/桌面/linux-5.8/drivers/study_driver_hq/cdev_test$
4 自旋锁
自旋锁(spin_lock)是一种典型的对临界资源进行互斥访问的手段,顾名思义,为了获得一个自旋锁,在某个CPU上运行的代码要先执行一个原子操作,该操作测试并设置某个内存变量,在操作完成之前其他执行单元不能访问到这个内存变量。
如果测试结果表明锁已经空闲,则该程序将会获得这个自旋锁并继续执行;如果测试表明锁被占用,程序将会在一个小的循环内重复这个“测试并设置”的操作,即“自旋”的动作,即原地打转,当自旋锁持有者通过重置该变量释放这个自旋锁之后,某个等待的“测试并设置”操作向其调用者报告锁已经被释放。
4.1 自旋锁API
/* 定义自旋锁 */
spinlock_t lock;
/* 动态初始化自旋锁 */
spin_lock_init(&lock);
/* 获得自旋锁,如果能够立刻获得锁就马上返回,否则它将自旋在那里直到锁被释放 */
spin_lock(lock);
/* 尝试获得自旋锁,如果能够立刻获得锁就返回真,否则立刻返回假,即退出原地打转的状态 */
spin_trylock(lock);
/* 释放自旋锁,与lock和trylock配合使用 */
spin_unlock(lock);
- 用法示例
spinlock_t lock;
spin_lock_init(lock);
spin_lock(lock);
... //临界区
spin_unlock(lock);
4.2 自旋锁操作示例
- 驱动代码
#include <linux/init.h>
#include <linux/module.h>
#include <linux/cdev.h>
#include <linux/fs.h>
#include <linux/device.h>
#include <asm/uaccess.h>
#include <linux/slab.h>
#include <linux/miscdevice.h>
#include <asm/atomic.h>
#include <linux/spinlock.h>
#include "cdev_test_v4.h"
#define BUF_SIZR 1024
#define MAJOR_NUM 168
#define MINOR_NUM 0
struct demo_cdev {
char *buffer; //my private memory
int value;
struct miscdevice *mdev;
spinlock_t lock;
int open_count; //要维护的变量值
};
/* step1: malloc memory for char device */
struct demo_cdev *demo_dev;
static ssize_t demo_read(struct file *file, char __user *buf, size_t size, loff_t *pos)
{
int ret = -1;
int read_bytes;
struct demo_cdev *demo = file->private_data;
char *kbuf = demo->buffer + *pos;
printk(KERN_INFO "Enter: %s\n", __func__);
/* determine whether user has finished reading data */
if (*pos >= BUF_SIZR) {
return 0;
}
if (size > (BUF_SIZR - *pos)) {
read_bytes = BUF_SIZR - *pos;
} else {
read_bytes = size;
}
ret = copy_to_user(buf, kbuf, read_bytes);
if (ret != 0) {
return -EFAULT;
}
*pos += read_bytes;
return read_bytes;
}
static ssize_t demo_write(struct file *file, const char __user *buf, size_t size, loff_t *pos)
{
int ret;
int write_bytes;
struct demo_cdev *demo = file->private_data;
char *kbuf = demo->buffer + *pos;
printk(KERN_INFO "Enter: %s\n", __func__);
if (*pos >= BUF_SIZR) {
return 0;
}
if (size > (BUF_SIZR - *pos)) {
write_bytes = BUF_SIZR - *pos;
} else {
write_bytes = size;
}
ret = copy_from_user(kbuf, buf, write_bytes);
if (ret != 0) {
return -EFAULT;
}
*pos += write_bytes;
return write_bytes;
}
static int demo_open(struct inode *node, struct file *file)
{
printk(KERN_INFO "Enter: %s\n", __func__);
spin_lock(&demo_dev->lock);
if (demo_dev->open_count) {
printk(KERN_ERR "already open\n");
/* 退出的时候需要解锁,不然会死锁 */
spin_unlock(&demo_dev->lock);
return -EBUSY;
}
demo_dev->open_count ++;
spin_unlock(&demo_dev->lock);
printk(KERN_INFO "open\n");
file->private_data = (void *)demo_dev;
return 0;
}
static int demo_release(struct inode *node, struct file *file)
{
struct demo_cdev *demo = (struct demo_cdev *)file->private_data;
printk(KERN_INFO "Enter: %s\n", __func__);
spin_lock(&demo->lock);
demo->open_count --;
spin_unlock(&demo->lock);
return 0;
}
static long demo_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
{
int ret = 0;
struct demo_cdev *demo = file->private_data;
switch(cmd) {
case CDEV_TEST_V4_CLEAN:
memset(demo->buffer, 0x00, BUF_SIZR);
printk(KERN_INFO "cmd: clean\n");
break;
case CDEV_TEST_V4_GETVAL:
put_user(demo->value, (int *)arg);
printk(KERN_INFO "cmd: getval\n");
break;
case CDEV_TEST_V4_SETVAL:
demo->value = (int)arg;
printk(KERN_INFO "cmd: setval\n");
break;
default:
break;
}
return (long)ret;
}
static struct file_operations demo_operation= {
.open = demo_open,
.release = demo_release,
.read = demo_read,
.write = demo_write,
.unlocked_ioctl = demo_ioctl,
};
static struct miscdevice misc_struct = {
.minor = MISC_DYNAMIC_MINOR,
.name = "misc_dev",
.fops = &demo_operation,
};
static int __init demo_init(void)
{
int ret = -1;
printk(KERN_INFO "Enter: %s\n", __func__);
demo_dev = (struct demo_cdev *)kmalloc(sizeof(struct demo_cdev), GFP_KERNEL);
if (!demo_dev) {
printk(KERN_ERR "failed to malloc demo_dev\n");
ret = -ENOMEM;
goto ERROR_MALLOC_DEMODEV;
}
demo_dev->value = 1;
demo_dev->buffer = (char *)kmalloc(BUF_SIZR, GFP_KERNEL);
if (!demo_dev->buffer) {
printk(KERN_ERR "malloc %d bytes failed\n", BUF_SIZR);
ret = -ENOMEM;
goto ERROR_MALLOC_BUFFER;
}
memset(demo_dev->buffer, 0x00, BUF_SIZR);
spin_lock_init(&demo_dev->lock);
demo_dev->mdev = &misc_struct;
demo_dev->open_count = 0;
ret = misc_register(demo_dev->mdev);
if (ret < 0) {
printk(KERN_ERR "failed to register misc\n");
goto ERROR_MISC;
}
printk(KERN_INFO "demo char device init done\n");
return 0;
ERROR_MISC:
kfree(demo_dev->buffer);
demo_dev->buffer = NULL;
ERROR_MALLOC_BUFFER:
kfree(demo_dev);
demo_dev = NULL;
ERROR_MALLOC_DEMODEV:
return ret;
}
static void __exit demo_exit(void)
{
printk(KERN_INFO "Enter: %s\n", __func__);
misc_deregister(demo_dev->mdev);
kfree(demo_dev->buffer);
demo_dev->buffer = NULL;
kfree(demo_dev);
demo_dev = NULL;
printk(KERN_INFO "demo char device exit done\n");
}
module_init(demo_init);
module_exit(demo_exit);
MODULE_AUTHOR("Qi Han");
MODULE_LICENSE("GPL");
- 驱动测试代码
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/ioctl.h>
#include <fcntl.h>
#include "cdev_test_v4.h"
#define DEVICE_PATH "/dev/misc_dev"
int main(void)
{
int fd;
int ret;
printf("opening...\n");
fd = open(DEVICE_PATH, O_RDWR);
if (fd < 0) {
printf("failed to open\n");
return -1;
}
printf("sleeping...\n");
sleep(7);
printf("closing\n");
close(fd);
return 0;
}
- 调试
hq@hq-virtual-machine:~/桌面/linux-5.8/drivers/study_driver_hq/cdev_test$ sudo insmod spin_lock.ko
hq@hq-virtual-machine:~/桌面/linux-5.8/drivers/study_driver_hq/cdev_test$ sudo chmod 666 /dev/misc_dev
hq@hq-virtual-machine:~/桌面/linux-5.8/drivers/study_driver_hq/cdev_test$ ./test
opening...
sleeping...
closing
hq@hq-virtual-machine:~/桌面/linux-5.8/drivers/study_driver_hq/cdev_test$ ./test &
[1] 8647
hq@hq-virtual-machine:~/桌面/linux-5.8/drivers/study_driver_hq/cdev_test$ opening...
sleeping...
hq@hq-virtual-machine:~/桌面/linux-5.8/drivers/study_driver_hq/cdev_test$ cat /dev/misc_dev
cat: /dev/misc_dev: 设备或资源忙
hq@hq-virtual-machine:~/桌面/linux-5.8/drivers/study_driver_hq/cdev_test$ closing
[1]+ 已完成 ./test
hq@hq-virtual-machine:~/桌面/linux-5.8/drivers/study_driver_hq/cdev_test$ dmesg
[18503.722177] open
[18505.514911] Enter: demo_open
[18505.514912] already open
[18513.726177] Enter: demo_release
4.2 自旋锁使用事项
- 自旋锁是忙等待,在得不到锁的时候会一直“测试并设置”,这样的话如果长时间得不到锁就会浪费系统资源,所以适合用在等待时间比较短的情况下,不然会降低系统性能
- 自旋锁可能会导致系统死锁,当2次试图获得这个自旋锁的时候,CPU会死锁
- 自旋锁锁定期间不能调用可能引起进程调度的函数,如果进程获得自旋锁之后再阻塞,如
copy_from_user()
、copy_to_user()
、kmalloc
、msleep
等函数,可能导致系统崩溃。因为当进程调度到其他设备的时候可能不会执行解锁操作,进而造成死锁
5 信号量
信号量(semaphore)是用于保护临界区的一种常用方法,它的用法与自旋锁类似,但是,与自旋锁不一样的是,当获取不到信号量时,进程不会原地打转,而是进入休眠状态。
5.1 信号量API
/* 定义信号量 */
struct semaphore sem;
/* 初始化信号量,该函数初始化信号量并设置信号量sem的值为val,尽管信号量可以被初始化为大于1的值进而成为计数信号量,但是通常不建议这么做??? */
void sema_init(struct semaphore *sem, int val);
/* 初始化一个互斥的信号量并把sem的值设为1 */
#define init_MUTEX(sem) sema_init(sem, 1)
DELCEAR_MUTEX(name)
/* 初始化一个信号量并把sem的值设为0 */
#define init_MUTEX_LOCKED(sem) sema_init(sem, 0)
DELCEAR_MUTEX_LOCKED(name)
/* 获取信号量,该函数用于获取信号量sem,他会导致睡眠,因此不能再中断上下文中使用,因为中断要求快进快出,假设在中断里面执行down函数,那么进程将会在中断里面休眠,进而使得CPU卡死在中断里面,无法跳出(中断优先级高,不可被打断) */
void down(struct semaphore *sem);
/* 获取信号量,因为down进入睡眠状态的进程不能被信号打断,但是因为down_interrruptible而进入睡眠状态的进程能被信号打断,信号也会导致该函数返回,返回非0。使用该函数的时候需要对返回值进行检查,如果为非0,通常立刻返回-ERESTARTSYS */
void down_interruptible(struct semaphore *sem);
/* 尝试获取信号量sem,如果能立刻获得就返回0,否则返回非0,不会导致调用者睡眠,可以在中断上下文中使用 */
int down_trylock(struct semaphore *sem);
/* 释放信号量,唤醒调用者 */
void up(struct semaphore *sem);
5.2 信号量操作示例
- 驱动代码
#include <linux/init.h>
#include <linux/module.h>
#include <linux/cdev.h>
#include <linux/fs.h>
#include <linux/device.h>
#include <asm/uaccess.h>
#include <linux/slab.h>
#include <linux/miscdevice.h>
#include <linux/semaphore.h>
#include "cdev_test_v4.h"
#define BUF_SIZR 1024
#define MAJOR_NUM 168
#define MINOR_NUM 0
struct demo_cdev {
char *buffer; //my private memory
int value;
struct miscdevice *mdev;
struct semaphore lock;
};
/* step1: malloc memory for char device */
struct demo_cdev *demo_dev;
static ssize_t demo_read(struct file *file, char __user *buf, size_t size, loff_t *pos)
{
int ret = -1;
int read_bytes;
struct demo_cdev *demo = file->private_data;
char *kbuf = demo->buffer + *pos;
printk(KERN_INFO "Enter: %s\n", __func__);
/* determine whether user has finished reading data */
if (*pos >= BUF_SIZR) {
return 0;
}
if (size > (BUF_SIZR - *pos)) {
read_bytes = BUF_SIZR - *pos;
} else {
read_bytes = size;
}
ret = copy_to_user(buf, kbuf, read_bytes);
if (ret != 0) {
return -EFAULT;
}
*pos += read_bytes;
return read_bytes;
}
static ssize_t demo_write(struct file *file, const char __user *buf, size_t size, loff_t *pos)
{
int ret;
int write_bytes;
struct demo_cdev *demo = file->private_data;
char *kbuf = demo->buffer + *pos;
printk(KERN_INFO "Enter: %s\n", __func__);
if (*pos >= BUF_SIZR) {
return 0;
}
if (size > (BUF_SIZR - *pos)) {
write_bytes = BUF_SIZR - *pos;
} else {
write_bytes = size;
}
ret = copy_from_user(kbuf, buf, write_bytes);
if (ret != 0) {
return -EFAULT;
}
*pos += write_bytes;
return write_bytes;
}
static int demo_open(struct inode *node, struct file *file)
{
printk(KERN_INFO "Enter: %s\n", __func__);
if (down_trylock(&demo_dev->lock)) {
printk(KERN_INFO "already open\n");
return -EBUSY;
}
file->private_data = (void *)demo_dev;
return 0;
}
static int demo_release(struct inode *node, struct file *file)
{
struct demo_cdev *demo = (struct demo_cdev*)(file->private_data);
printk(KERN_INFO "Enter: %s\n", __func__);
up(&demo->lock);
return 0;
}
static long demo_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
{
int ret = 0;
struct demo_cdev *demo = file->private_data;
switch(cmd) {
case CDEV_TEST_V4_CLEAN:
memset(demo->buffer, 0x00, BUF_SIZR);
printk(KERN_INFO "cmd: clean\n");
break;
case CDEV_TEST_V4_GETVAL:
put_user(demo->value, (int *)arg);
printk(KERN_INFO "cmd: getval\n");
break;
case CDEV_TEST_V4_SETVAL:
demo->value = (int)arg;
printk(KERN_INFO "cmd: setval\n");
break;
default:
break;
}
return (long)ret;
}
static struct file_operations demo_operation= {
.open = demo_open,
.release = demo_release,
.read = demo_read,
.write = demo_write,
.unlocked_ioctl = demo_ioctl,
};
static struct miscdevice misc_struct = {
.minor = MISC_DYNAMIC_MINOR,
.name = "misc_dev",
.fops = &demo_operation,
};
static int __init demo_init(void)
{
int ret = -1;
printk(KERN_INFO "Enter: %s\n", __func__);
demo_dev = (struct demo_cdev *)kmalloc(sizeof(struct demo_cdev), GFP_KERNEL);
if (!demo_dev) {
printk(KERN_ERR "failed to malloc demo_dev\n");
ret = -ENOMEM;
goto ERROR_MALLOC_DEMODEV;
}
demo_dev->value = 1;
demo_dev->buffer = (char *)kmalloc(BUF_SIZR, GFP_KERNEL);
if (!demo_dev->buffer) {
printk(KERN_ERR "malloc %d bytes failed\n", BUF_SIZR);
ret = -ENOMEM;
goto ERROR_MALLOC_BUFFER;
}
memset(demo_dev->buffer, 0x00, BUF_SIZR);
/* init for semaphore lock */
sema_init(&demo_dev->lock, 1);
demo_dev->mdev = &misc_struct;
ret = misc_register(demo_dev->mdev);
if (ret < 0) {
printk(KERN_ERR "failed to register misc\n");
goto ERROR_MISC;
}
printk(KERN_INFO "demo char device init done\n");
return 0;
ERROR_MISC:
kfree(demo_dev->buffer);
demo_dev->buffer = NULL;
ERROR_MALLOC_BUFFER:
kfree(demo_dev);
demo_dev = NULL;
ERROR_MALLOC_DEMODEV:
return ret;
}
static void __exit demo_exit(void)
{
printk(KERN_INFO "Enter: %s\n", __func__);
misc_deregister(demo_dev->mdev);
kfree(demo_dev->buffer);
demo_dev->buffer = NULL;
kfree(demo_dev);
demo_dev = NULL;
printk(KERN_INFO "demo char device exit done\n");
}
module_init(demo_init);
module_exit(demo_exit);
MODULE_AUTHOR("Qi Han");
MODULE_LICENSE("GPL");
- 测试代码:同自旋锁
- 调试
hq@hq-virtual-machine:~/桌面/linux-5.8/drivers/study_driver_hq/cdev_test$ sudo insmod semaphore.ko
hq@hq-virtual-machine:~/桌面/linux-5.8/drivers/study_driver_hq/cdev_test$ sudo chm
chmem chmod
hq@hq-virtual-machine:~/桌面/linux-5.8/drivers/study_driver_hq/cdev_test$ sudo chmod 666 /dev/misc_dev
hq@hq-virtual-machine:~/桌面/linux-5.8/drivers/study_driver_hq/cdev_test$ cat /dev/misc_dev
hq@hq-virtual-machine:~/桌面/linux-5.8/drivers/study_driver_hq/cdev_test$ ./test &
[1] 9715
hq@hq-virtual-machine:~/桌面/linux-5.8/drivers/study_driver_hq/cdev_test$ opening...
sleeping...
hq@hq-virtual-machine:~/桌面/linux-5.8/drivers/study_driver_hq/cdev_test$ cat /dev/misc_dev
cat: /dev/misc_dev: 设备或资源忙
hq@hq-virtual-machine:~/桌面/linux-5.8/drivers/study_driver_hq/cdev_test$ cat /dev/misc_dev
cat: /dev/misc_dev: 设备或资源忙
hq@hq-virtual-machine:~/桌面/linux-5.8/drivers/study_driver_hq/cdev_test$ closing
[1]+ 已完成 ./test
hq@hq-virtual-machine:~/桌面/linux-5.8/drivers/study_driver_hq/cdev_test$ cat /dev/misc_dev
hq@hq-virtual-machine:~/桌面/linux-5.8/drivers/study_driver_hq/cdev_test$
5.3 信号量 VS 自旋锁
- 同
- 二者都是解决互斥问题的基本手段,面对特定情况我们需要加以抉择
- 异
- 信号量是进程级的,用于多个进程之间资源的互斥,如果竞争失败,将会发生进程上下文切换(发生休眠),因为进程上下文切换开销较大,因此只有当前进程用资源时间较长的时候,选用信号量才是较好的选择
- 而当我们所保护的临界资源访问时间比较短的时候,使用自旋锁比较方便,它不会引起进程睡眠而导致上下文切换
5.4 总结
- 如果访问临界资源的时间比较长,那么我们可以选择信号量,否则使用自旋锁
- 信号量所保护的临界资源可能包含可能引起阻塞的代码,而自旋锁则绝对要避免这样的代码,阻塞意味着需要进行进程上下文切换,如果进程被切换出去,这时候如果另外一个进程想要获取自旋锁的话就会引起死锁
- 信号量存在于进程上下文,因此,如果被保护的资源需要在中断或者软中断的情况下使用,则只能选择自旋锁
6 完成量
6.1 信号量用于同步
如果我们将信号量初始化为0,那么它可用于同步。同步即需要执行单元拥有某特定的执行顺序,需要保证执行的先后顺序。此处以如下代码段为例子,在执行单元A中首先将互斥量初始化为0,故在执行down
函数的时候执行单元A会休眠,直到执行单元B执行up
函数释放信号量之后,执行单元A才会获得信号量,进而继续执行代码区域b。也就是说执行单元A的执行需要执行单元B的唤醒,进而实现了同步。当然linux也为我们提供了一个完成量来实现同步。
执行单元A 执行单元B
struct semphore sem;
init_MUTEX_LOCKED(&sem);/*初始化信号量为0*/ 代码区域c
代码区域a
down(&sem); <------激活---------- up(&sem);
代码区域b
6.2 完成量API
/* step1:定义完成量 */
struct completion my_completion;
/* step2:初始化完成量 */
init_completion(&my_completion);
DECLARE_COMPLETION(my_completion);
/* step3:等待完成量 */
void wait_for_completion(struct completion *c);
/* step4:唤醒完成量 */
void complete(struct completion *c); //唤醒一个等待的执行单元
void complete_all(struct completion *c); //唤醒所有等待的执行单元
6.3 完成量操作示例
- 驱动代码,此处驱动代码在read函数处阻塞等待完成量,而在write函数处去唤醒完成量。也就是说应用层代码在读的时候会阻塞,一旦向内核写入数据后才能完成读的操作,即先写后读,进而达到同步状态。
#include <linux/init.h>
#include <linux/module.h>
#include <linux/cdev.h>
#include <linux/fs.h>
#include <linux/device.h>
#include <asm/uaccess.h>
#include <linux/slab.h>
#include <linux/miscdevice.h>
#include <linux/completion.h>
#include "cdev_test_v4.h"
#define BUF_SIZR 1024
#define MAJOR_NUM 168
#define MINOR_NUM 0
struct demo_cdev {
char *buffer; //my private memory
int value;
struct miscdevice *mdev;
struct completion com;
};
/* step1: malloc memory for char device */
struct demo_cdev *demo_dev;
static ssize_t demo_read(struct file *file, char __user *buf, size_t size, loff_t *pos)
{
int ret = -1;
int read_bytes;
struct demo_cdev *demo = file->private_data;
char *kbuf = demo->buffer + *pos;
printk(KERN_INFO "Enter: %s\n", __func__);
printk(KERN_INFO "Waitting for completion...\n");
wait_for_completion(&demo->com);
printk(KERN_INFO "Waitting done\n");
/* determine whether user has finished reading data */
if (*pos >= BUF_SIZR) {
return 0;
}
if (size > (BUF_SIZR - *pos)) {
read_bytes = BUF_SIZR - *pos;
} else {
read_bytes = size;
}
ret = copy_to_user(buf, kbuf, read_bytes);
if (ret != 0) {
return -EFAULT;
}
*pos += read_bytes;
return read_bytes;
}
static ssize_t demo_write(struct file *file, const char __user *buf, size_t size, loff_t *pos)
{
int ret;
int write_bytes;
struct demo_cdev *demo = file->private_data;
char *kbuf = demo->buffer + *pos;
printk(KERN_INFO "Enter: %s\n", __func__);
if (*pos >= BUF_SIZR) {
return 0;
}
if (size > (BUF_SIZR - *pos)) {
write_bytes = BUF_SIZR - *pos;
} else {
write_bytes = size;
}
ret = copy_from_user(kbuf, buf, write_bytes);
if (ret != 0) {
return -EFAULT;
}
*pos += write_bytes;
complete(&demo->com);
return write_bytes;
}
static int demo_open(struct inode *node, struct file *file)
{
printk(KERN_INFO "Enter: %s\n", __func__);
file->private_data = (void *)demo_dev;
return 0;
}
static int demo_release(struct inode *node, struct file *file)
{
printk(KERN_INFO "Enter: %s\n", __func__);
return 0;
}
static long demo_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
{
int ret = 0;
struct demo_cdev *demo = file->private_data;
switch(cmd) {
case CDEV_TEST_V4_CLEAN:
memset(demo->buffer, 0x00, BUF_SIZR);
printk(KERN_INFO "cmd: clean\n");
break;
case CDEV_TEST_V4_GETVAL:
put_user(demo->value, (int *)arg);
printk(KERN_INFO "cmd: getval\n");
break;
case CDEV_TEST_V4_SETVAL:
demo->value = (int)arg;
printk(KERN_INFO "cmd: setval\n");
break;
default:
break;
}
return (long)ret;
}
static struct file_operations demo_operation= {
.open = demo_open,
.release = demo_release,
.read = demo_read,
.write = demo_write,
.unlocked_ioctl = demo_ioctl,
};
static struct miscdevice misc_struct = {
.minor = MISC_DYNAMIC_MINOR,
.name = "misc_dev",
.fops = &demo_operation,
};
static int __init demo_init(void)
{
int ret = -1;
printk(KERN_INFO "Enter: %s\n", __func__);
demo_dev = (struct demo_cdev *)kmalloc(sizeof(struct demo_cdev), GFP_KERNEL);
if (!demo_dev) {
printk(KERN_ERR "failed to malloc demo_dev\n");
ret = -ENOMEM;
goto ERROR_MALLOC_DEMODEV;
}
demo_dev->value = 1;
demo_dev->buffer = (char *)kmalloc(BUF_SIZR, GFP_KERNEL);
if (!demo_dev->buffer) {
printk(KERN_ERR "malloc %d bytes failed\n", BUF_SIZR);
ret = -ENOMEM;
goto ERROR_MALLOC_BUFFER;
}
memset(demo_dev->buffer, 0x00, BUF_SIZR);
init_completion(&demo_dev->com);
demo_dev->mdev = &misc_struct;
ret = misc_register(demo_dev->mdev);
if (ret < 0) {
printk(KERN_ERR "failed to register misc\n");
goto ERROR_MISC;
}
printk(KERN_INFO "demo char device init done\n");
return 0;
ERROR_MISC:
kfree(demo_dev->buffer);
demo_dev->buffer = NULL;
ERROR_MALLOC_BUFFER:
kfree(demo_dev);
demo_dev = NULL;
ERROR_MALLOC_DEMODEV:
return ret;
}
static void __exit demo_exit(void)
{
printk(KERN_INFO "Enter: %s\n", __func__);
misc_deregister(demo_dev->mdev);
kfree(demo_dev->buffer);
demo_dev->buffer = NULL;
kfree(demo_dev);
demo_dev = NULL;
printk(KERN_INFO "demo char device exit done\n");
}
module_init(demo_init);
module_exit(demo_exit);
MODULE_AUTHOR("Qi Han");
MODULE_LICENSE("GPL");
- 调试过程,当我们
cat
读取数据的时候进程会阻塞,直到我们向内核写入数据后读的线程才会退出阻塞状态。
hq@hq-virtual-machine:~/桌面/linux-5.8/drivers/study_driver_hq/cdev_test$ sudo insmod completion.ko
[sudo] hq 的密码:
hq@hq-virtual-machine:~/桌面/linux-5.8/drivers/study_driver_hq/cdev_test$ sudo chmod 666 /dev/misc_dev
hq@hq-virtual-machine:~/桌面/linux-5.8/drivers/study_driver_hq/cdev_test$ cat /dev/misc_dev &
[1] 5602
hq@hq-virtual-machine:~/桌面/linux-5.8/drivers/study_driver_hq/cdev_test$ echo "hanqi" -> /dev/misc_dev
hanqi -
hq@hq-virtual-machine:~/桌面/linux-5.8/drivers/study_driver_hq/cdev_test$ dmesg
[ 4463.854757] Enter: demo_init
[ 4463.854864] demo char device init done
[ 4490.500256] Enter: demo_open
[ 4490.500268] Enter: demo_read
[ 4490.500269] Waitting for completion...
[ 4546.197831] Enter: demo_open
[ 4546.197841] Enter: demo_write
[ 4546.197859] Enter: demo_release
[ 4546.197907] Waitting done
[ 4546.197989] Enter: demo_release
7 互斥锁
虽然我们可以通过信号量完成互斥的操作,但是在linux中也为我们提供了一套标准的mutex互斥锁机制
7.1 互斥锁API
/* 定义互斥锁 */
struct mutex my_mutex;
/* 初始化互斥锁 */
mutex_init(&my_mutex);
/* 获取互斥体(上锁) */
void inline __sched mutex_lock(struct mutex *lock);
int __sched mutex_lock_interruptible(struct mutex *lock);
int __sched mutex_trylock(struct mutex *lock);
/* 释放互斥锁 */
void __sched mutex_unlock(struct mutex *lock);
/* 1:__sched宏展开后如下,将带有__sched的函数放到,sched.text段,即如果不想让函数在waiting channel中显示出来,就应该加上__sched */
/* Attach to any functions which should be ignored in wchan output */
#define __sched __attribute__((__section__(".sched.text")))
7.2 wchan
- kernel中有个waiting channel,如果用户空间的进程休眠了,可以查到是停在内核空间哪个函数中等待,命令为:
cat "/proc/<pid>/wchan"
,进而.sched.text
段的代码是会被wchan所忽略的,schedule这个函数是不会出现在wchan的结果中 - 举个例子,在完成量的例子中,我们在read函数中让函数休眠等待完成量,此刻我们可执行
cat "/proc/<pid>/wchan"
查看进程死在了哪个函数中,显然结果是read
函数中
demo_readhq@hq-virtual-machine:~/桌面/linux-5.8/drivers/study_driver_hq/cdev_test$ cat /dev/misc_dev
demo_readhq@hq-virtual-machine:~/桌面/linux-5.8/drivers/study_driver_hq/cdev_test$ ps aux
hq 5664 0.0 0.0 16860 592 pts/0 D+ 12:27 0:00 cat /dev/misc_dev
hq 5665 2.0 0.0 20140 3348 pts/1 R+ 12:27 0:00 ps aux
hq@hq-virtual-machine:~/桌面/linux-5.8/drivers/study_driver_hq/cdev_test$ cat "/proc/5664/wchan"
demo_read
hq@hq-virtual-machine:~/桌面/linux-5.8/drivers/study_driver_hq/cdev_test$
7.3 互斥锁操作示例
- 驱动代码
#include <linux/init.h>
#include <linux/module.h>
#include <linux/cdev.h>
#include <linux/fs.h>
#include <linux/device.h>
#include <asm/uaccess.h>
#include <linux/slab.h>
#include <linux/miscdevice.h>
#include <linux/mutex.h>
#include "cdev_test_v4.h"
#define BUF_SIZR 1024
#define MAJOR_NUM 168
#define MINOR_NUM 0
struct demo_cdev {
char *buffer; //my private memory
int value;
struct miscdevice *mdev;
struct mutex lock;
};
/* step1: malloc memory for char device */
struct demo_cdev *demo_dev;
static ssize_t demo_read(struct file *file, char __user *buf, size_t size, loff_t *pos)
{
int ret = -1;
int read_bytes;
struct demo_cdev *demo = file->private_data;
char *kbuf = demo->buffer + *pos;
printk(KERN_INFO "Enter: %s\n", __func__);
/* lock */
mutex_lock(&demo->lock);
/* determine whether user has finished reading data */
if (*pos >= BUF_SIZR) {
return 0;
}
if (size > (BUF_SIZR - *pos)) {
read_bytes = BUF_SIZR - *pos;
} else {
read_bytes = size;
}
ret = copy_to_user(buf, kbuf, read_bytes);
if (ret != 0) {
mutex_unlock(&demo->lock);
return -EFAULT;
}
*pos += read_bytes;
/* unlock */
mutex_unlock(&demo->lock);
return read_bytes;
}
static ssize_t demo_write(struct file *file, const char __user *buf, size_t size, loff_t *pos)
{
int ret;
int write_bytes;
struct demo_cdev *demo = file->private_data;
char *kbuf = demo->buffer + *pos;
printk(KERN_INFO "Enter: %s\n", __func__);
/* lock */
mutex_lock(&demo->lock);
if (*pos >= BUF_SIZR) {
return 0;
}
if (size > (BUF_SIZR - *pos)) {
write_bytes = BUF_SIZR - *pos;
} else {
write_bytes = size;
}
ret = copy_from_user(kbuf, buf, write_bytes);
if (ret != 0) {
mutex_unlock(&demo->lock);
return -EFAULT;
}
*pos += write_bytes;
/* unlock */
mutex_unlock(&demo->lock);
return write_bytes;
}
static int demo_open(struct inode *node, struct file *file)
{
printk(KERN_INFO "Enter: %s\n", __func__);
file->private_data = (void *)demo_dev;
return 0;
}
static int demo_release(struct inode *node, struct file *file)
{
printk(KERN_INFO "Enter: %s\n", __func__);
return 0;
}
static long demo_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
{
int ret = 0;
struct demo_cdev *demo = file->private_data;
switch(cmd) {
case CDEV_TEST_V4_CLEAN:
memset(demo->buffer, 0x00, BUF_SIZR);
printk(KERN_INFO "cmd: clean\n");
break;
case CDEV_TEST_V4_GETVAL:
put_user(demo->value, (int *)arg);
printk(KERN_INFO "cmd: getval\n");
break;
case CDEV_TEST_V4_SETVAL:
demo->value = (int)arg;
printk(KERN_INFO "cmd: setval\n");
break;
default:
break;
}
return (long)ret;
}
static struct file_operations demo_operation= {
.open = demo_open,
.release = demo_release,
.read = demo_read,
.write = demo_write,
.unlocked_ioctl = demo_ioctl,
};
static struct miscdevice misc_struct = {
.minor = MISC_DYNAMIC_MINOR,
.name = "misc_dev",
.fops = &demo_operation,
};
static int __init demo_init(void)
{
int ret = -1;
printk(KERN_INFO "Enter: %s\n", __func__);
demo_dev = (struct demo_cdev *)kmalloc(sizeof(struct demo_cdev), GFP_KERNEL);
if (!demo_dev) {
printk(KERN_ERR "failed to malloc demo_dev\n");
ret = -ENOMEM;
goto ERROR_MALLOC_DEMODEV;
}
demo_dev->value = 1;
demo_dev->buffer = (char *)kmalloc(BUF_SIZR, GFP_KERNEL);
if (!demo_dev->buffer) {
printk(KERN_ERR "malloc %d bytes failed\n", BUF_SIZR);
ret = -ENOMEM;
goto ERROR_MALLOC_BUFFER;
}
memset(demo_dev->buffer, 0x00, BUF_SIZR);
mutex_init(&demo_dev->lock);
demo_dev->mdev = &misc_struct;
ret = misc_register(demo_dev->mdev);
if (ret < 0) {
printk(KERN_ERR "failed to register misc\n");
goto ERROR_MISC;
}
printk(KERN_INFO "demo char device init done\n");
return 0;
ERROR_MISC:
kfree(demo_dev->buffer);
demo_dev->buffer = NULL;
ERROR_MALLOC_BUFFER:
kfree(demo_dev);
demo_dev = NULL;
ERROR_MALLOC_DEMODEV:
return ret;
}
static void __exit demo_exit(void)
{
printk(KERN_INFO "Enter: %s\n", __func__);
misc_deregister(demo_dev->mdev);
kfree(demo_dev->buffer);
demo_dev->buffer = NULL;
kfree(demo_dev);
demo_dev = NULL;
printk(KERN_INFO "demo char device exit done\n");
}
module_init(demo_init);
module_exit(demo_exit);
MODULE_AUTHOR("Qi Han");
MODULE_LICENSE("GPL");