Linux设备驱动工程师之路——简单字符设备驱动程序
K-Style
转载请注明来自于衡阳师范学院08电2 K-Style http://blog.csdn.net/ayangke,QQ:843308498 邮箱:yangkeemail@qq.com
一、重要知识点
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 <linux/module.h>
#include <linux/types.h>
#include <linux/fs.h>
#include <linux/errno.h>
#include <linux/mm.h>
#include <linux/sched.h>
#include <linux/init.h>
#include <linux/cdev.h>
#include <asm/io.h>
#include <asm/system.h>
#include <asm/uaccess.h>
#define MEMDEV_MAJOR 251
#define MEMDEV_NUM 2
#define MEMDEV_SIZE 1024
struct mem_dev
{
unsignedint size;
char*data;
structsemaphore sem;
};
static int mem_major = MEMDEV_MAJOR;
struct cdev mem_cdev;
struct mem_dev *mem_devp;
static int mem_open(struct inode *inode,struct file *filp)
{
structmem_dev *dev;
unsignedint num;
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;
}
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<0)|| (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<MEMDEV_NUM; i++){
mem_devp[i].size= MEMDEV_SIZE;
mem_devp[i].data= kmalloc(MEMDEV_SIZE, GFP_KERNEL);
memset(mem_devp[i].data,0, MEMDEV_SIZE);
init_MUTEX(&mem_devp[i].sem);//初始化互斥锁
}
returnresult;
fail_malloc:
unregister_chrdev_region(MKDEV(mem_major,0), MEMDEV_NUM);
returnresult;
}
static void memdev_exit(void)
{
cdev_del(&mem_cdev);
unregister_chrdev_region(MKDEV(mem_major,0), MEMDEV_NUM);//注意释放的设备号个数一定要和申请的设备号个数保存一致
//否则会导致设备号资源流失
printk("memdev_exit\n");
}
module_init(memdev_init);
module_exit(memdev_exit);
MODULE_AUTHOR("Y-Kee");
MODULE_LICENSE("GPL");
三、疑点难点
1.
__init
__initdata
__exit
__exitdata
仅用于模块初始化或清除阶段的函数(__init和__exit)和数据(__initdata和__exitdata)标记。标记为初始化项目会在初始化结束后丢弃;而退出在内核未被配置为可卸载模块的情况下被简单的丢弃。被标记为__exit的函数只能在模块卸载或者系统关闭时被调用,其他任何用法都是错误的。内核通过对应的目标对象放置在可执行文件的特殊ELF段中而让这些标记起作用。
2.static
初始化函数应该被声明为static,因为这种函数在特定文件之外没有其他意义。因为一个模块函数要对内核其他部分课件,则必须显示导出,因此这并不是什么强制性规则。
3. struct module *owner
内核使用这个字段以避免在模块操作正在使用时卸载该模块。几乎在所有的情况下,该成员都会被初始化为THIS_MODULE。它是定义在<linux/module.h>中的一个宏。
4 __user
我们会注意到许多参数包括含有__user字串,它其实是一种形式的文档而已,表面指针是一个用户指针,因此不能被直接用。对通常的编译来讲,__user没有任何效果,但是可由外部检查软件使用,用来寻找对用户空间地址错误使用。
分享到:
相关推荐
这本书的CHM格式版本——"Linux设备驱动程序第三版2.chm"包含了丰富的技术细节和实例,为读者提供了全面的学习资源。 在Linux操作系统中,设备驱动程序是连接硬件和操作系统内核的桥梁,它们负责管理和控制硬件设备...
《LINUX 设备驱动程序(第三版)》是Linux设备驱动开发领域的一本经典著作,旨在帮助读者深入理解Linux内核机制以及如何编写高效的设备驱动程序。这本书详细讲解了Linux系统下驱动程序的设计原理和实现方法,对于软件...
### Linux下PCI设备驱动程序开发相关知识点 #### 一、PCI总线系统体系结构 **PCI总线简介:** PCI(Peripheral Component Interconnect)是一种通用的总线接口标准,在计算机系统中广泛应用。它提供了一组完整的...
由于提供的文件内容无法直接查看,我将基于标题和描述中提供的信息——“嵌入式linux驱动开发工程师学习路线”,来详细阐述这个领域内的关键知识点,以及成为一名合格的嵌入式Linux驱动开发工程师所需掌握的技能和...
这里我们关注的两本权威书籍——"Linux设备驱动程序第三版" 和 "LINUX内核设计与实现",将帮助我们深入探索这个主题。 "Linux设备驱动程序第三版" 是一本经典的教程,由Jonathan Corbet、Greg Kroah-Hartman和...
3. **实例分析**:通过具体案例来分析字符设备驱动的编写步骤和技术细节,包括初始化、处理文件操作请求等关键环节。 #### 四、驱动程序安装与使用 1. **安装方式**: - **模块方式**:将驱动程序编译成模块,...
在“亚嵌嵌入式Linux就业班-(第十周)驱动程序开发”这一主题中,我们聚焦于嵌入式系统中的关键部分——驱动程序开发。驱动程序是操作系统与硬件设备之间的桥梁,它允许软件应用程序利用硬件的功能。在这个课程中,...
### 嵌入式设计及Linux驱动开发指南——基于ARM9处理器 #### 一、嵌入式系统概述 嵌入式系统是指将计算机系统嵌入到其他设备中,使其成为这些设备的一个组成部分,并且能够执行特定功能的系统。这类系统广泛应用于...
它不仅提供了丰富的理论知识,还包含了大量实践案例和技巧,非常适合希望深入了解Linux驱动开发的工程师和技术爱好者阅读。通过对上述关键知识点的理解和掌握,读者可以更好地编写高质量的驱动程序,并参与到Linux...
它详细讲解了如何编写和理解Linux设备驱动程序,包括字符设备、块设备和网络设备等,涵盖了I/O子系统、中断处理、DMA、设备模型等内容。 2. **《深入分析Linux内核源代码.pdf》**:对于希望了解Linux内核工作机制的...
- **设备驱动程序开发**:探讨如何编写Linux设备驱动程序,包括字符设备和块设备。 - **内核模块编程**:介绍如何编写和加载内核模块,实现特定功能。 #### 五、编写理念与特色 - **实用性与优化并重**:本书不仅...
本例程——“myMemDev1”则是针对Mini2440设计的一个内存模拟字符设备驱动程序,旨在帮助开发者理解字符设备驱动的基本原理和操作方法。 字符设备驱动是操作系统与硬件之间的重要桥梁,它使得用户空间的应用程序...
第11章“嵌入式Linux设备驱动开发”深入探讨了Linux设备驱动的原理和编写,包括字符设备、块设备和网络设备驱动,是理解和开发硬件接口的关键部分。 最后,第12章“Qt图形编程基础”介绍了使用Qt框架进行图形用户...
2. 驱动分类:字符设备驱动、块设备驱动、网络设备驱动等。 3. 驱动框架:如Platform Driver、I2C Driver、SPI Driver等。 4. DMA(Direct Memory Access):高效的数据传输机制。 5. 中断处理:理解和编写中断服务...
5. **设备驱动**:注释会讲解如何编写和集成设备驱动程序,以便内核可以与各种硬件设备通信,如网络接口卡、显卡、声卡等。 6. **网络栈**:Linux内核的网络子系统处理网络协议,从链路层到应用层,包括TCP/IP协议...
一种是将I2C设备当作普通字符设备处理,这种方法简单直接,但需要对I2C设备和适配器操作有深入了解,且程序移植性较差。另一种是利用Linux的I2C驱动体系结构,这种方法虽然需要理解复杂的子系统操作,但能更好地利用...
本书深入浅出地讲解了 Linux 设备驱动程序的设计与实现原理,并提供了大量的实践案例,旨在帮助读者全面掌握 Linux 驱动程序开发技巧。 #### 核心知识点解析 ##### 1. **Linux 内核与设备驱动的关系** - **内核...
"Linux设备驱动开发详解(第二版)"这本书籍会详细介绍如何编写和调试驱动程序,包括字符设备驱动、块设备驱动、网络设备驱动等。同时,"【正点原子】I.MX6U嵌入式Linux驱动开发指南"则针对特定的I.MX6U处理器提供了...
《ldd3中文版》是Linux系统编程领域的一本经典著作,主要讲解了Linux设备驱动程序的开发。这本书是Linux Device Drivers(简称LDD)的第三版,针对2.6内核进行了详细的阐述。"ldd"在Linux中通常指链接器动态调试工具...
2. **设备驱动模型**:讲解Linux设备驱动模型,如字符设备、块设备、网络设备等,以及如何注册和管理设备驱动。 3. **GPIO驱动**:I.MX6U处理器中的GPIO(通用输入/输出)驱动编写,如何控制硬件接口的开关和状态。...