实验五设备驱动中的并发控制.docx
- 文档编号:2650774
- 上传时间:2023-05-04
- 格式:DOCX
- 页数:18
- 大小:106.54KB
实验五设备驱动中的并发控制.docx
《实验五设备驱动中的并发控制.docx》由会员分享,可在线阅读,更多相关《实验五设备驱动中的并发控制.docx(18页珍藏版)》请在冰点文库上搜索。
实验五设备驱动中的并发控制
实验五:
设备驱动中的并发控制
1.并发与竞态
并发(concurrency)指的是多个执行单元同时、并行被执行,而并发的执行单元对共享资源(硬件资源和软件上的全局变量、静态变量等)的访问则很容易导致竞态(raceconditions)。
例如,对于globalmem设备,假设一个执行单元A对其写入3000个字符“a”,而另一个执行单元B对其写入4000个字符“b”,第三个执行单元C读取globalmem的所有字符。
如果执行单元A、B的写操作如图1所示的顺序执行,执行单元C的读操作不会有问题。
但是,如果执行单元A、B如图2所示的顺序执行,而执行单元C又“不合时宜”地读,则会读出3000个“b”。
比图2更复杂、更混乱的并发大量地存在于设备驱动中,只要并发的多个执行单元存在对共享资源的访问,竞态就可能发生。
在Linux内核中,主要的竞态发生于如下几种情况。
1.对称多处理器(SMP)的多个CPU
SMP是一种紧耦合、共享存储的系统模型,它的特点是多个CPU使用共同的系统总线,因此可访问共同的外设和储存器。
2.单CPU内进程与抢占它的进程
Linux2.6内核支持抢占调度,一个进程在内核执行的时候可能被另一高优先级进程打断,进程与抢占它的进程访问共享资源的情况类似于SMP的多个CPU。
3.中断(硬中断、软中断、Tasklet、底半部)与进程之间
中断可以打断正在执行的进程,如果中断处理程序访问进程正在访问的资源,则竞态也会发生。
此外,中断也有可能被新的更高优先级的中断打断,因此,多个中断之间本身也可能引起并发而导致竞态。
上述并发的发生情况除了SMP是真正的并行以外,其他的都是“宏观并行、微观串行”的,但其引发的实质问题和SMP相似。
解决竞态问题的途径是保证对共享资源的互斥访问,所谓互斥访问是指一个执行单元在访问共享资源的时候,其他的执行单元被禁止访问。
访问共享资源的代码区域称为临界区(criticalsections),临界区需要以某种互斥机制加以保护。
中断屏蔽、原子操作、自旋锁和信号量等是Linux设备驱动中可采用的互斥途径,下节将讲解信号量是如何实现对资源的互斥访问的。
2.信号量
信号量(semaphore)是用于保护临界区的一种常用方法。
只有得到信号量的进程才能执行临界区代码,当获取不到信号量时,进程进入休眠等待状态。
Linux系统中与信号量相关的操作主要有如下4种。
1.定义信号量
下列代码定义名称为sem的信号量。
structsemaphoresem;
其中结构体semaphore的定义为:
structsemaphore
{
spinlock_tlock;
unsignedintcount;
structlist_headwait_list;
};
2.初始化信号量
voidsema_init(structsemaphore*sem,intval);
该函数初始化信号量,并设置信号量sem的值为val。
尽管信号量可以被初始化为大于1的值从而成为一个计数信号量,但是它通常不被这样使用。
voidinit_MUTEX(structsemaphore*sem);
该函数用于初始化一个用于互斥的信号量,它把信号量sem的值设置为1,等同于sema_init(structsemaphore*sem,1)。
voidinit_MUTEX_LOCKED(structsemaphore*sem);
该函数也用于初始化一个信号量,但它把信号量sem的值设置为0,等同于sema_init(structsemaphore*sem,0)。
此外,下面两个宏是定义并初始化信号量的“快捷方式”。
DECLARE_MUTEX(name)
DECLARE_MUTEX_LOCKED(name)
前者定义一个名为name的信号量并初始化为1,后者定义一个名为name的信号量并初始化为0。
3.获得信号量
voiddown(structsemaphore*sem);
该函数用于获得信号量sem,它会导致睡眠,因此不能在中断上下文使用。
intdown_interruptible(structsemaphore*sem);
该函数功能与down()类似,不同之处为,因为down()而进入睡眠状态的进程不能被信号打断,而因为down_interruptible()而进入睡眠状态的进程能被信号打断,信号也会导致该函数返回,这时候函数的返回值非0。
intdown_trylock(structsemaphore*sem);
该函数尝试获得信号量sem,如果能够立刻获得,它就获得该信号量并返回0,否则,返回非0值。
它不会导致调用者睡眠,可以在中断上下文使用。
在使用down_interruptible()获取信号量时,对返回值一般会进行检查,如果非0,通常立即返回-ERESTARTSYS,如:
if(down_interruptible(&sem))
{
return-ERESTARTSYS;
}
4.释放信号量
voidup(structsemaphore*sem);
该函数释放信号量sem,唤醒等待者。
信号量一般这样被使用,如下所示:
//定义信号量
DECLARE_MUTEX(mount_sem);
down(&mount_sem);//获取信号量,保护临界区
...
criticalsection//临界区
...
up(&mount_sem);//释放信号量
以下代码给出了使用信号量实现设备只能被一个进程打开的例子。
staticDECLARE_MUTEX(xxx_lock);//定义互斥锁
staticintxxx_open(structinode*inode,structfile*filp)
{
...
if(down_trylock(&xxx_lock))//获得打开锁
return-EBUSY;//设备忙
...
return0;/*成功*/
}
staticintxxx_release(structinode*inode,structfile*filp)
{
up(&xxx_lock);//释放打开锁
return0;
}
3.增加并发控制后的globalmem驱动
在globalmem()的读写函数中,由于要调用copy_from_user()、copy_to_user()这些可能导致阻塞的函数,因此使用信号量。
驱动工程师习惯将某设备所使用的自旋锁、信号量等辅助手段也放在设备结构中,因此,可如代码清单1那样修改globalmem_dev结构体的定义,并在模块初始化函数中初始化这个信号量,如代码清单2所示。
1.增加并发控制后的globalmem设备结构体
structglobalmem_dev
{
structcdevcdev;/*cdev结构体*/
unsignedcharmem[GLOBALMEM_SIZE];/*全局内存*/
structsemaphoresem;/*并发控制用的信号量*/
};
2.增加并发控制后的globalmem设备驱动模块加载函数
intglobalmem_init(void)
{
intresult;
dev_tdevno=MKDEV(globalmem_major,0);
/*申请设备号*/
if(globalmem_major)
result=register_chrdev_region(devno,1,"globalmem");
else/*动态申请设备号*/
{
result=alloc_chrdev_region(&devno,0,1,"globalmem");
globalmem_major=MAJOR(devno);
}
if(result<0)
returnresult;
/*动态申请设备结构体的内存*/
globalmem_devp=kmalloc(sizeof(structglobalmem_dev),GFP_KERNEL);
if(!
globalmem_devp)/*申请失败*/
{
result=-ENOMEM;
gotofail_malloc;
}
memset(globalmem_devp,0,sizeof(structglobalmem_dev));
globalmem_setup_cdev(globalmem_devp,0);
init_MUTEX(&globalmem_devp->sem);
return0;
fail_malloc:
unregister_chrdev_region(devno,1);
returnresult;
}
在访问globalmem_dev中的共享资源时,需先获取这个信号量,访问完成后,随即释放这个信号量。
驱动中新的globalmem读、写操作如代码清单3所示。
staticssize_tglobalmem_read(structfile*filp,char__user*buf,size_tsize,loff_t*ppos)
{
unsignedlongp=*ppos;
unsignedintcount=size;
intret=0;
structglobalmem_dev*dev=filp->private_data;/*获得设备结构体指针*/
/*分析和获取有效的写长度*/
if(p>=GLOBALMEM_SIZE)
returncount?
-ENXIO:
0;
if(count>GLOBALMEM_SIZE-p)
count=GLOBALMEM_SIZE-p;
if(down_interruptible(&dev->sem))
{
return-ERESTARTSYS;
}
/*内核空间->用户空间*/
if(copy_to_user(buf,(void*)(dev->mem+p),count))
{
ret=-EFAULT;
}
else
{
*ppos+=count;
ret=count;
printk(KERN_INFO"read%dbytes(s)from%d\n",count,p);
}
up(&dev->sem);//释放信号量
returnret;
}
staticssize_tglobalmem_write(structfile*filp,constchar__user*buf,size_tsize,loff_t*ppos)
{
unsignedlongp=*ppos;
unsignedintcount=size;
intret=0;
structglobalmem_dev*dev=filp->private_data;/*获得设备结构体指针*/
/*分析和获取有效的写长度*/
if(p>=GLOBALMEM_SIZE)
returncount?
-ENXIO:
0;
if(count>GLOBALMEM_SIZE-p)
count=GLOBALMEM_SIZE-p;
if(down_interruptible(&dev->sem))//获得信号量
{
return-ERESTARTSYS;
}
/*用户空间->内核空间*/
if(copy_from_user(dev->mem+p,buf,count))
ret=-EFAULT;
else
{
*ppos+=count;
ret=count;
printk(KERN_INFO"written%dbytes(s)from%d\n",count,p);
}
up(&dev->sem);//释放信号量
returnret;
}
代码第16~19和第53~56行用于获取信号量,如果down_interruptible()返回值非0,则意味着其在获得信号量之前已被打断,这时写函数返回-ERESTARTSYS。
代码第33和第67行用于在对临界资源访问结束后释放信号量。
除了globalmem的读写操作之外,如果在读写的同时,另一执行单元执行MEM_CLEARIO控制命令,也会导致全局内存的混乱,因此,globalmem_ioctl()函数也需被重写,如代码清单4所示。
/*ioctl设备控制函数*/
staticintglobalmem_ioctl(structinode*inodep,structfile*filp,unsigned
intcmd,unsignedlongarg)
{
structglobalmem_dev*dev=filp->private_data;/*获得设备结构体指针*/
switch(cmd)
{
caseMEM_CLEAR:
if(down_interruptible(&dev->sem))
{
return-ERESTARTSYS;
}
memset(dev->mem,0,GLOBALMEM_SIZE);
up(&dev->sem);//释放信号量
printk(KERN_INFO"globalmemissettozero\n");
break;
default:
return-EINVAL;
}
return0;
}
实验
1.在实验四中同时打开两个终端,分别执行test程序,观察实验现象,注意命令的执行顺序(以红色数字标注)。
可发现执行的结果跟预期不一致。
2.在/dev/book/globalmem中新建文件globalmen.c。
代码如下:
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#defineGLOBALMEM_SIZE0x1000/*全局内存最大4K字节*/
#defineMEM_CLEAR0x1/*清0全局内存*/
#defineGLOBALMEM_MAJOR150/*预设的globalmem的主设备号*/
staticintglobalmem_major=GLOBALMEM_MAJOR;
/*globalmem设备结构体*/
structglobalmem_dev
{
structcdevcdev;/*cdev结构体*/
unsignedcharmem[GLOBALMEM_SIZE];/*全局内存*/
structsemaphoresem1;/*并发控制用的信号量*/
structsemaphoresem2;/*并发控制用的信号量*/
};
structglobalmem_dev*globalmem_devp;
intglobalmem_open(structinode*inode,structfile*filp)
{
filp->private_data=globalmem_devp;
structglobalmem_dev*dev=filp->private_data;
if(down_interruptible(&dev->sem2))//down_trylock(&dev->sem2)
{
return-EBUSY;
}
return0;
}
intglobalmem_release(structinode*inode,structfile*filp)
{
structglobalmem_dev*dev=filp->private_data;
up(&dev->sem2);
return0;
}
staticintglobalmem_ioctl(structinode*inodep,structfile*filp,unsigned
intcmd,unsignedlongarg)
{
structglobalmem_dev*dev=filp->private_data;
switch(cmd)
{
caseMEM_CLEAR:
if(down_interruptible(&dev->sem1))
{
return-ERESTARTSYS;
}
memset(dev->mem,0,GLOBALMEM_SIZE);
up(&dev->sem1);
printk(KERN_INFO"globalmemissettozero\n");
break;
default:
return-EINVAL;
}
return0;
}
staticssize_tglobalmem_read(structfile*filp,char__user*buf,size_tsize,loff_t*ppos)
{
unsignedlongp=*ppos;
unsignedintcount=size;
intret=0;
structglobalmem_dev*dev=filp->private_data;
if(p>=GLOBALMEM_SIZE)
returncount?
-ENXIO:
0;
if(count>GLOBALMEM_SIZE-p)
count=GLOBALMEM_SIZE-p;
if(down_interruptible(&dev->sem1))
{
return-ERESTARTSYS;
}
if(copy_to_user(buf,(void*)(dev->mem+p),count))
{
ret=-EFAULT;
}
else
{
*ppos+=count;
ret=count;
printk(KERN_INFO"read%dbytes(s)from%d\n",count,p);
}
up(&dev->sem1);
returnret;
}
staticssize_tglobalmem_write(structfile*filp,constchar__user*buf,size_tsize,loff_t*ppos)
{
unsignedlongp=*ppos;
unsignedintcount=size;
intret=0;
structglobalmem_dev*dev=filp->private_data;
if(p>=GLOBALMEM_SIZE)
returncount?
-ENXIO:
0;
if(count>GLOBALMEM_SIZE-p)
count=GLOBALMEM_SIZE-p;
if(down_interruptible(&dev->sem1))
{
return-ERESTARTSYS;
}
if(copy_from_user(dev->mem+p,buf,count))
ret=-EFAULT;
else
{
*ppos+=count;
ret=count;
printk(KERN_INFO"written%dbytes(s)from%d\n",count,p);
}
up(&dev->sem1);
returnret;
}
staticconststructfile_operationsglobalmem_fops=
{
.owner=THIS_MODULE,
.read=globalmem_read,
.write=globalmem_write,
.ioctl=globalmem_ioctl,
.open=globalmem_open,
.release=globalmem_release,
};
staticvoidglobalmem_setup_cdev(structglobalmem_dev*dev,intindex)
{
interr,devno=MKDEV(globalmem_major,index);
cdev_init(&dev->cdev,&globalmem_fops);
dev->cdev.owner=THIS_MODULE;
dev->cdev.ops=&globalmem_fops;
err=cdev_add(&dev->cdev,devno,1);
if(err)
printk(KERN_NOTICE"Error%daddingLED%d",err,index);
}
intglobalmem_init(void)
{
intresult;
dev_tdevno=MKDEV(globalmem_major,0);
if(globalmem_major)
result=register_chrdev_region(devno,1,"globalmem");
else
{
result=alloc_chrdev_region(&devno,0,1,"globalmem");
globalmem_major=MAJOR(devno);
}
if(result<0)
- 配套讲稿:
如PPT文件的首页显示word图标,表示该PPT已包含配套word讲稿。双击word图标可打开word文档。
- 特殊限制:
部分文档作品中含有的国旗、国徽等图片,仅作为作品整体效果示例展示,禁止商用。设计者仅对作品中独创性部分享有著作权。
- 关 键 词:
- 实验 设备 驱动 中的 并发 控制