驱动之路-简单字符设备驱动程序

一、重要知识点
1. 主次设备号
dev_t
dev_t是内核中用来表示设备编号的数据类型;
int major(dev_t dev)
int minor(dev_t dev)
这两个宏抽取主次设备号。
dev­_t mkdev(unsigned int major, unsignedint minor)
这个宏由主/次设备号构造一个dev_t结构。
2. 分配和释放设备号
int register_chardev_region(dev_t first,unsigned int count, char *name)
静态申请设备号。
int alloc_chardev_region(dev_t *dev,unsigned int firstminor, unsigned int count, char *name)
动态申请设备号,注意第一个参数是传地址,而静态则是传值。
3. 几种重要的数据结构
struct file
file结构代表一个打开的文件,它由内核在open时创建,并传递给该文件上进行操作的所有函数,直到最后的close函数。
file结构private_data是跨系统调用时保存状态信息非常有用的资源。
file结构的f_ops 保存了文件的当前读写位置。
struct inode
内核用inode代表一个磁盘上的文件,它和file结构不同,后者表示打开的文件描述符。对于单个文件,可能会有许多个表示打开文件的文件描述符file结构,但他们都指单个inode结构。inode的dev_t i_rdev成员包含了真正的设备编号,struct cdev *i_cdev包含了指向struct cdev结构的指针。
struct file_operations
file_operations结构保存了字符设备驱动程序的方法。
4. 字符设备的注册和注销
struct cdev *cdev_alloc(void);
void cdev_init(struct cdev *dev, structfile_operations *fops);
int cdev_add(struct cdev *dev, dev_t num,unsigned int count);
void cdev_del(struct cdev *dev);
用来管理cdev结构的函数,内核中使用该结构表示字符设备。注意cdev_add函数的count参数为次设备的个数,要想拥有多个次设备,就必须将该参数设为次设备的个数。
5. 并发处理
信号量和自旋锁的区别,使用信号量时当调用进程试图获得一个锁定了的锁时会导致进程睡眠,而自旋锁则是一直循法的等待一直到该锁解锁了为止。
1)信号量
declare_mutex(name);
declare_mutex_locked(name);
声明和初始化用在互斥模式中的信号量的两个宏
void init_mutex(struct semaphore *sem)
void init_mutex_locker(struct semaphore*sem);
这两个函数可以在运行时初始化信号量
void down(struct semaphore *sem);
int down_interruptible(struct semaphore*sem);
int down_trylock(struct semahpore *sem);
void up(struct semaphore *sem);
锁定和解锁信号量。如果必要,down会将调用进程置于不可中断的休眠状态;相反,down_interruptible可被信号中断。down_trylock不会休眠,并且会在信号量不可用时立即返回。锁定信号量的代码最后必须使用up解锁该信号量。
2)自旋锁
spionlock_t lock = spin_lock_unlocked;
spin_lock_init(spinlock_t *lock);
初始化自旋锁的两种方式。
voidspin_lock(spinlock_t *lock);
锁定自旋锁
voidspin_unlock(spinlock_t *lock);
解锁自旋锁
二、驱动代码
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#definememdev_major251
#definememdev_num2
#definememdev_size1024
structmem_dev
{
unsignedintsize;
char*data;
structsemaphoresem;
};
staticintmem_major=memdev_major;
structcdevmem_cdev;
structmem_dev*mem_devp;
staticintmem_open(structinode*inode,structfile*filp)
{
structmem_dev*dev;
unsignedintnum;
printk(mem_open.\n);
num=minor(inode->i_rdev);//获得次设备号
if(num>(memdev_num-1))//检查次设备号有效性
return-enodev;
dev=&mem_devp[num];
filp->private_data=dev;//将设备结构保存为私有数据
return0;
}
staticintmem_release(structinode*inode,structfile*filp)
{
printk(mem_release.\n);
return0;
}
staticssize_tmem_read(structfile*filp,char__user*buf,size_tsize,loff_t*ppos)
{
intret=0;
structmem_dev*dev;
unsignedlongp;
unsignedlongcount;
printk(mem_read.\n);
dev=filp->private_data;//获得设备结构
count=size;
p=*ppos;
//检查偏移量和数据大小的有效性
if(p>memdev_size)
return0;
if(count>(memdev_size-p))
count=memdev_size-p;
if(down_interruptible(&dev->sem))//锁定互斥信号量
return-erestartsys;
//读取数据到用户空间
if(copy_to_user(buf,dev->data+p,count)){
ret=-efault;
printk(copyfromuserfailed\n);
}
else{
*ppos+=count;
ret=count;
printk(read%dbytesfromdev\n,count);
}
up(&dev->sem);//解锁互斥信号量
returnret;
}
staticssize_tmem_write(structfile*filp,constchar__user*buf,size_tsize,loff_t*ppos)//注意:第二个参数和read方法不同
{
intret=0;
structmem_dev*dev;
unsignedlongp;
unsignedlongcount;
printk(mem_write.\n);
dev=filp->private_data;
count=size;
p=*ppos;
if(p>memdev_size)
return0;
if(count>(memdev_size-p))
count=memdev_size-p;
if(down_interruptible(&dev->sem))//锁定互斥信号量
return-erestartsys;
if(copy_from_user(dev->data+p,buf,count)){
ret=-efault;
printk(copyfromuserfailed\n);
}
else{
*ppos+=count;
ret=count;
printk(write%dbytestodev\n,count);
}
up(&dev->sem);//解锁互斥信号量
returnret;
}
staticloff_tmem_llseek(structfile*filp,loff_toffset,intwhence)
{
intnewpos;
printk(mem_llseek.\n);
switch(whence)
{
case0:
newpos=offset;
break;
case1:
newpos=filp->f_pos+offset;
break;
case2:
newpos=memdev_size-1+offset;
break;
default:
return-einval;
}
if((newpos(memdev_size-1)))
return-einval;
filp->f_pos=newpos;
returnnewpos;
}
staticconststructfile_operationsmem_fops={
.owner=this_module,
.open=mem_open,
.write=mem_write,
.read=mem_read,
.release=mem_release,
.llseek=mem_llseek,
};
staticint__initmemdev_init(void)
{
intresult;
interr;
inti;
//申请设备号
dev_tdevno=mkdev(mem_major,0);
if(mem_major)
result=register_chrdev_region(devno,memdev_num,memdev);//注意静态申请的dev_t参数和动态dev_t参数的区别
else{//静态直接传变量,动态传变量指针
result=alloc_chrdev_region(&devno,0,memdev_num,memdev);
mem_major=major(devno);
}
if(result<0){
printk(can'tgetmajordevno:%d\n,mem_major);
returnresult;
}
//注册设备驱动
cdev_init(&mem_cdev,&mem_fops);
mem_cdev.owner=this_module;
err=cdev_add(&mem_cdev,mkdev(mem_major,0),memdev_num);//如果有n个设备就要添加n个设备号
if(err)
printk(addcdevfaild,erris%d\n,err);
//分配设备内存
mem_devp=kmalloc(memdev_num*(sizeof(structmem_dev)),gfp_kernel);
if(!mem_devp){
result=-enomem;
gotofail_malloc;
}
memset(mem_devp,0,memdev_num*(sizeof(structmem_dev)));
for(i=0;ii_rdev);//获得次设备号if(num> (memdev_num -1)) //检查次设备号有效性return-enodev;dev= &mem_devp[num];filp->private_data= dev; //将设备结构保存为私有数据return0;}static int mem_release(struct inode *inode,struct file *filp){printk(mem_release.\n);return0;}static ssize_t mem_read(struct file *filp,char __user *buf, size_t size, loff_t *ppos){intret = 0;structmem_dev *dev;unsignedlong p;unsignedlong count;printk(mem_read.\n);dev= filp->private_data;//获得设备结构count= size;p= *ppos;//检查偏移量和数据大小的有效性if(p> memdev_size)return0;if(count> (memdev_size-p))count= memdev_size - p;if(down_interruptible(&dev->sem))//锁定互斥信号量return -erestartsys;//读取数据到用户空间if(copy_to_user(buf,dev->data+p, count)){ret= -efault;printk(copyfrom user failed\n);}else{*ppos+= count;ret= count;printk(read%d bytes from dev\n, count);}up(&dev->sem);//解锁互斥信号量returnret;}static ssize_t mem_write(struct file *filp,const char __user *buf, size_t size, loff_t *ppos)//注意:第二个参数和read方法不同{intret = 0;structmem_dev *dev;unsignedlong p;unsignedlong count;printk(mem_write.\n);dev= filp->private_data;count= size;p= *ppos;if(p> memdev_size)return0;if(count> (memdev_size-p))count= memdev_size - p;if(down_interruptible(&dev->sem))//锁定互斥信号量return-erestartsys;if(copy_from_user(dev->data+p,buf, count)){ret= -efault;printk(copyfrom user failed\n);}else{*ppos+= count;ret= count;printk(write%d bytes to dev\n, count);}up(&dev->sem);//解锁互斥信号量returnret;}static loff_t mem_llseek(struct file *filp,loff_t offset, int whence){intnewpos;printk(mem_llseek.\n);switch(whence){case0:newpos= offset;break;case1:newpos= filp->f_pos + offset;break;case2:newpos= memdev_size - 1 + offset;break;default:return-einval;}if((newpos(memdev_size - 1)))return-einval;filp->f_pos= newpos;returnnewpos;}static const struct file_operationsmem_fops = {.owner= this_module,.open= mem_open,.write= mem_write,.read= mem_read,.release= mem_release,.llseek= mem_llseek,};static int __init memdev_init(void){intresult;interr;inti;//申请设备号dev_tdevno = mkdev(mem_major, 0);if(mem_major)result= register_chrdev_region(devno, memdev_num, memdev);//注意静态申请的dev_t参数和动态dev_t参数的区别else{ //静态直接传变量,动态传变量指针result= alloc_chrdev_region(&devno, 0, memdev_num, memdev);mem_major= major(devno);}if(result< 0){printk(can'tget major devno:%d\n, mem_major);returnresult;}//注册设备驱动cdev_init(&mem_cdev,&mem_fops);mem_cdev.owner= this_module;err= cdev_add(&mem_cdev, mkdev(mem_major, 0), memdev_num);//如果有n个设备就要添加n个设备号if(err)printk(addcdev faild,err is %d\n, err);//分配设备内存mem_devp= kmalloc(memdev_num*(sizeof(struct mem_dev)), gfp_kernel);if(!mem_devp){result = - enomem;goto fail_malloc;}memset(mem_devp,0, memdev_num*(sizeof(struct mem_dev)));for(i=0;i
三、疑点难点
1.
__init
__initdata
__exit
__exitdata
仅用于模块初始化或清除阶段的函数(__init和__exit)和数据(__initdata和__exitdata)标记。标记为初始化项目会在初始化结束后丢弃;而退出在内核未被配置为可卸载模块的情况下被简单的丢弃。被标记为__exit的函数只能在模块卸载或者系统关闭时被调用,其他任何用法都是错误的。内核通过对应的目标对象放置在可执行文件的特殊elf段中而让这些标记起作用。
2.static
初始化函数应该被声明为static,因为这种函数在特定文件之外没有其他意义。因为一个模块函数要对内核其他部分课件,则必须显示导出,因此这并不是什么强制性规则。
3. struct module *owner
内核使用这个字段以避免在模块操作正在使用时卸载该模块。几乎在所有的情况下,该成员都会被初始化为this_module。它是定义在中的一个宏。
4 __user
我们会注意到许多参数包括含有__user字串,它其实是一种形式的文档而已,表面指针是一个用户指针,因此不能被直接用。对通常的编译来讲,__user没有任何效果,但是可由外部检查软件使用,用来寻找对用户空间地址错误使用。

符号模拟电路中的错误诊断设计方法
重磅 |芯华章发布多款新产品,打造全面数字验证解决方案
为什么软件操作都会,却不会实际工程问题仿真
基于微惯性传感器的高灵敏度随动控制技术
四曲面的小米6,颜值方面会超过vivo xplay6吗?
驱动之路-简单字符设备驱动程序
C&K今天宣布推出其新 PT 系列密封电源钮子开关
220V 150W音响电源电子火牛直流变压器电路
全球首个5G商业化的国家?韩国将于4月5日推出5G电信服务
重低音功放电路图
PMSM电机的FOC实现原理解析
雷达探测器和GPS雷达探测器的原理分析和功能比较
32.768khz晶振时间跑不准有偏差的原因
shell脚本基本命令
facebook数据泄露事件致人员架构重组 将成立区块链业务部门
如何利用区块链和智能合约技术使实现自动化交易
oppo vivo超越华为尚难,但是已经赶超小米
位移传感器在自动调焦单元有什么应用
为何选择车载以太网 车载以太网技术的演进
谈一谈Linux让实时任务独占CPU的事