linux设备驱动中的并发控制

并发指的是多个执行单元同时、并行被执行,而并发的执行单元对共享资源(硬件资源和软件上的全局变量,静态变量等)的访问很容易导致竞态。在linux内核中,竞态发生主要有以下几种情况:

  1. 对称多处理器(SMP)的多个CPU之间
  2. 单CPU内核进程与抢占他的进程
  3. 中断(硬中断、软终端、Tasklet、底半部)与进程之间

解决竞态问题的途径是保证对共享资源的互斥访问,所谓互斥访问是指一个执行单元在访问共享资源的时候,其他的执行单元被禁止访问。访问共享资源的代码区域称为临界区,临界区需要被某种互斥机构加以保护。中断屏蔽、原子操作、自旋锁、信号量、互斥体等是linux设备驱动中可采用的互斥途径。

针对这个问题,做一个不正确,但可以形象说明这个问题例子:

现在对之前写的简单字符设备驱动的代码做一个简单测试,在一个打开的字符设备驱动中,一个线程一直不停的往驱动中写aaaa,另一个线程不停的读取设备中的内容,按照所写的驱动代码可知,读取线程会一直读到写线程所写的内容;但是如果在开启一个写线程一直不停的写bbbb,那么读线程读到的内容还会一直是aaaa吗?

测试代码如下:

#include <stdio.h>
#include <pthread.h>
#include <stdlib.h>
#include <fcntl.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <time.h>
#include <string.h>

void func_pth1(void *arg)
{
    int *fd = arg;
    while(1)
        write(*fd,"aaaa",sizeof("aaaa"));
}

void func_pth2(void *arg)
{
    int *fd = arg;
    while(1)
        write(*fd,"bbbb",sizeof("bbbb"));
}

void func_pth3(void *arg)
{
    int *fd = arg;
    char buf[4];
    while(1){
        read(*fd,buf,4);
        for(int i=0;i<4;i++)
            printf("%c",buf[i]);
        printf("\n");
    }
}

int main()
{
    pthread_t pth1,pth2,pth3;
    int ret_pth1,ret_pth2,ret_pth3;

    int fd = open("/dev/mymodules",O_RDWR);
    if(fd <0)
        printf("fd open error\n");

    ret_pth1 = pthread_create(&pth1,NULL,(void *)func_pth1,(void *)&fd);
    ret_pth2 = pthread_create(&pth2,NULL,(void *)func_pth2,(void *)&fd);
    ret_pth3 = pthread_create(&pth3,NULL,(void *)func_pth3,(void *)&fd);
    
    if(ret_pth1 != 0)
        printf("pth1 create error\n");
    if(ret_pth2 != 0)
        printf("pth2 create error\n");
    if(ret_pth3 != 0)
        printf("pth3 create error\n");

    int tmp1 = pthread_join(pth1,NULL);
    int tmp2 = pthread_join(pth2,NULL);
    int tmp3 = pthread_join(pth3,NULL);

    return 0;
}

交叉编译,移植到虚拟开发版中运行:

打印中若出现aabb的存在,while(1)的去写,到底是哪个线程写入了设备驱动了,这时我们想到的是内核的调度机制,时间片轮转,内核最小执行单元(线程)的执行状态,线程的执行机制等等。但是,如果把写入aaaa的线程换成是往银行卡存钱,写入bbbb的线程换成从银行卡取钱,读线程为柜台显示卡余额,那最后显示的余额怎么算了?

当然,都不希望这种现象的发生,linux内核也不希望哈,于是就有了防止这种单CPU进程抢占等竞态的方法。这些方法中,中断屏蔽很少单独使用,原子操作只能针对整型数进行,信号量已基本不用,因此自旋锁和互斥体应用最为广泛。自旋锁或导致死循环(死锁),锁定期间不允许阻塞,因此要求锁定的临界区小。互斥体允许临界区阻塞,可以使用临界区大的情况。这里给出自旋锁和互斥体选用的3项原则:

  1. 当锁不能被获取到时,使用互斥体的开销是进程上下文切换的时间,使用自旋锁的开销是等待获取自旋锁(由临界区执行时间决定)。若临界区较大,宜使用互斥体,反之使用自旋锁。
  2. 互斥体所保护的临界区克包含可能引起阻塞的代码,而自旋锁则绝对要避免用来保护这样代码的临界区。应为阻塞意味着进程的切换,如果进程切换出去后,另一个进程企图过去本自旋锁,死锁就会发生。
  3. 互斥体存在于进程上下文,因此,如果被保护的资源要再中断或软中断情况下使用,只能使用自旋锁。当然使用互斥体代码mutex_trylock()方式,不能获取就立即返回以避免阻塞。

互斥体的使用

//定义并初始化互斥体my_mutex
struct mutex my_nutex;
mutex_init(&my_mutex);

//获取互斥体
void mutex_lock(&my_mutex); //引起的睡眠不能被中断打断
int mutex_lock_interruptible(&my_mutex);//睡眠可以被中断打断
int mutex_trylock(&my_mutex);//尝试获取mutex,获取不到不会引起进程睡眠

//释放互斥体
void mutex_unlock(&my_mutex);

自旋锁的使用

//定义与初始化自旋锁
spinlock_t lock;
spin_lock_init(lock);

//获取自旋锁
spin_lock(lock);

//释放自旋锁
spin_unlock(lock);

自旋锁还有一些衍生的锁,有兴趣可以多了解一些,在之前写的简单字符设备驱动代码中使用了copy_to_user()等会导致阻塞的函数,因此使用互斥体来防止竞态,修改代码如下:

//添加头文件
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/types.h>
#include <linux/fs.h>
#include <linux/errno.h>
#include <linux/mm.h>
#include <linux/sched.h>
#include <linux/cdev.h>
#include <asm/io.h>
#include <linux/slab.h>
#include <asm/uaccess.h>
#include <linux/device.h>
 
static int major=0,minor=0;
 
//定义cdev结构体
struct mymodule_dev{
    struct cdev cdev;
    unsigned char buf[512];
    struct mutex mutex;
};
 
struct mymodule_dev *mydev;
struct class *my_class;
struct device *my_device;
 
static int mymodule_open(struct inode *inode,struct file *filp)
{
    filp->private_data = mydev;
    return 0;
}
 
static int mymodule_release(struct inode *inode,struct file *filp)
{
    return 0;
}
 
static ssize_t mymodule_read(struct file *filp,char __user *buf,size_t size,loff_t *ppos)
{
    unsigned long p = *ppos;
    unsigned int count = size;
    int ret = 0;
    struct mymodule_dev *dev = filp->private_data;
    nutex_lock(&dev->mutex);
    if(copy_to_user(buf,dev->buf,count))
        return -EFAULT;
    else
    {
        *ppos += count;
        ret = count;
        printk(KERN_INFO"read %d bytes from %ld\n",count,p);
    }
    mutex_unlock(&dev->mutex);
    return ret;
}
 
static ssize_t mymodule_write(struct file *filp,const char __user *buf,size_t size,loff_t *ppos)
{
    unsigned long p = *ppos;
    unsigned int count = size;
    int ret = 0;
    struct mymodule_dev *dev = filp->private_data;
    mutex_lock(&dev->mutex);
    if(copy_from_user(dev->buf,buf,count))
        return -EFAULT;
    else
    {
        *ppos += count;
        ret = count;
        printk(KERN_INFO"write %d bytes from %ld\n",count,p);
    }
    mutex_unlock(&dev->mutex);
    return ret;
}
 
//file_operation设备驱动文件操作结构体
static struct file_operations mymodule_fops = {
    .owner = THIS_MODULE,
    .open = mymodule_open,
    .release = mymodule_release,
    .read = mymodule_read,
    .write = mymodule_write,
};
 
//初始化并添加cdev结构体
static void mymodule_cdev_setup(struct mymodule_dev *dev)
{
    int err,devno=MKDEV(major,minor);
    //初始化
    cdev_init(&dev->cdev,&mymodule_fops);
    dev->cdev.owner = THIS_MODULE;
    dev->cdev.ops = &mymodule_fops;
    //注册,添加
    err = cdev_add(&dev->cdev,devno,1);
    if(err)
        printk(KERN_NOTICE"error %d adding mymodule",err);
}
 
//模块加载
int __init mymodule_init(void)
{
    //申请设备号
    int result;
    dev_t devno = MKDEV(major,minor);
    if(major)
        result = register_chrdev_region(devno,1,"mymodule");
    else{
        result = alloc_chrdev_region(&devno,minor,1,"mymodule");
        major = MAJOR(devno);
        minor = MINOR(devno);
    }
    if(result<0)
        return result;
    //动态申请设备结构体内存
    mydev = kmalloc(sizeof(struct mymodule_dev),GFP_KERNEL);
    if(!mydev){
        result=-ENOMEM;
        goto fail_malloc;
    }
    memset(mydev,0,sizeof(struct mymodule_dev));
    //初始化互斥体
    mutex_init(&mydev->mutex); 
   //cdev字符设备的初始化和添加
    mymodule_cdev_setup(mydev);
 
    //注册设备节点
    my_class = class_create(THIS_MODULE,"mymodule_t");
    my_device = device_create(my_class,NULL,MKDEV(major,minor),NULL,"mymodules");
 
    return 0;
fail_malloc:unregister_chrdev_region(devno,1);
return result;  
}
//模块卸载
void __exit mymodule_exit(void)
{
    device_destroy(my_class,MKDEV(major,minor));
    class_destroy(my_class);
    //删除cdev结构体
    cdev_del(&mydev->cdev);
    kfree(mydev);
    //注销设备号
    unregister_chrdev_region(MKDEV(major,minor),1);
}
 
module_init(mymodule_init);
module_exit(mymodule_exit);
MODULE_AUTHOR("HaoRan");
MODULE_LICENSE("GPL");

重新编译驱动并加载,在测试上述测试代码看结果,并想为什么?

发布了45 篇原创文章 · 获赞 7 · 访问量 1万+

猜你喜欢

转载自blog.csdn.net/weixin_42005898/article/details/104751299
今日推荐