linux字符设备驱动程序

来源:互联网 发布:收支软件下载 编辑:程序博客网 时间:2024/05/17 07:26

字符设备驱动程序:

主要有以下知识点:设备号、创建设备文件、重要数据结构、设备注册、设备操作。

设备号分主设备号和次设备号,可以进入/dev/ 中进行查看(ll)已经存在的设备文件。

主设备号是将 字符设备文件 和字符设备驱动 关联的关键。

主设备号用来反映设备类型,次设备号用来区分同类型的设备。

内核中 设备号 的字符类型是 dev_t ,其实是unsigned int 32位整数,其中高12位为主设备号,低20位为次设备号。

分解出主设备号的宏:MAJOR(dev_t dev);分解出次设备号的宏:MINOR(dev_t dev);结合成设备号的宏:MKDEV(ma,mi) //ma为主设备号,mi为次设备号

如何申请主设备号:静态申请,动态分配。

静态申请:

根据Documentation/devices.txt,确定一个没有使用的主设备号;

使用 register_chrdev_region 函数注册设备号。缺点:一旦驱动被广泛使用,这个随机选定的主设备号可能会导致设备号冲突,而使驱动程序无法注册。

int register_chrdev_region(dev_t from, unsigned count, const char *name);//申请使用从from开始的count个设备号(主设备号不变,次设备号增加),name设备名(体现在/proc/devices/)。

动态分配:

使用alloc_chrdev_region分配设备号。

int alloc_chrdev_region(dev_t *dev, unsigned baseminor, unsigned count,const char *name);//请求内核动态分配count个设备号,且次设备号从baseminor开始,dev为分配到的设备号,name为设备名(体现在/proc/devices)。

注销设备号:

void unregister_chrdev_region(dev_t from, unsigned count) ;//释放从from开始的count个设备号。

字符设备的注册或注销:

在lunux 2.6内核中,字符设备使用 struct cdev 来描述。字符设备的注册可分为如下三个步骤:1、分配cdev 2、初始化cdev 3、设备注册(添加cdev) 。

分配cdev:struct cdev *cdev_alloc(void);也可以直接定义变量。

初始化cdev:void cdev_init(struct cdev *cdev, const struct file_operations *fops) ;参数:cdev: 待初始化的cdev结构,fops: 设备对应的操作函数集。

添加cdev:int cdev_add(struct cdev *p, dev_t dev, unsigned count);参数:p: 待添加到内核的字符设备结构;dev: 设备号;count: 添加的设备个数。

通过以上的设备注册之后,insmod相应的模块之后,在/proc/devices会找到相应的设备以及其设备号(当然在rmmod之后就会取消)。

设备的注销:intcdev_del(structcdev *p);参数:p: 要注销的字符设备结构。

重要数据结构:

在linux字符设备驱动程序设计中,有三种非常重要的数据结构:struct file、struct inode、struct file_operations。

struct file:代表一个打开的文件,系统中每个打开的文件在内核空间都有一个关联的 struct file,它由内核在打开文件时创建(即使是同样的文件被打开两次也会有两个struct file类型的变量被创建),在文件关闭后释放。

其中重要的成员loff_t f_pos /*文件读写位置,和lseek相似*/ 和 struct file_operations *f_op

struct inode:用来记录文件的物理上的信息。因此,它和代表打开文件的file结构是不同的。一个文件可以对应多个file结构,但只有一个inode结构。重要成员:dev_t i_rdev:设备号。

struct file_operations:一个函数指针的集合,定义能在设备上进行的操作。结构中的成员指向驱动中的函数,这些函数实现一个特别的操作,对于不支持的操作保留为NULL。

设备操作,接下来必须得对struct file_operations *fops类型的变量进行初始化,其中函数指针指向的函数有:

int (*open)(structinode *, struct file *);在设备文件上的第一个操作,并不要求驱动程序一定要实现这个方法。如果该项为NULL,设备的打开操作还是可以成功。

void (*release)(struct inode *, struct file *);当设备文件被关闭时调用这个操作。与open相仿,release也可以没有。

ssize_t (*read) (struct file *, char __user *, size_t, loff_t *);从设备中读取数据。

ssize_t (*write) (struct file *, const char __user *, size_t, loff_t *);向设备发送数据。

unsigned int (*poll) (struct file *, struct poll_table_struct *);对应select系统调用

int (*ioctl) (structinode *, struct file *,unsigned int, unsigned long);控制设备

int (*mmap)(struct file *, struct vm_area_struct *);将设备映射到进程虚拟地址空间中。

loff_t (*llseek) (struct file *,loff_t,int);修改文件的当前读写位置,并将新位置作为返回值。

Open方法:

Open方法是驱动程序用来为以后的操作完成初始化准备工作的。在大部分驱动程序中,open完成如下工作:初始化设备。标明次设备号。

Release方法:

Release方法的作用正好与open相反。这个设备方法有时也称为close,它应该:关闭设备。

读和写:

ssize_t xxx_read(struct file * filp, char __user *buff, size_t count,loff_t * offp);
ssize_t xxx_write(struct file *filp, char __user *buff, size_t count,loff_t *offp);
对于 2 个方法, filp是文件指针,  count是请求传输的数据量。buff 参数指向数据缓存。最后, offp 指出文件当前的访问位置。

读和写方法都完成类似的工作:从设备中读取数据到用户空间;将数据传递给驱动程序。它们的原型也相当相似:

Read 和 Write 方法的 buff 参数是用户空间指针。因此, 它不能被内核代码直接引用,理由如下:用户空间指针在内核空间时可能根本是无效的---没有那个地址的映射。内核调用用户空间的指针必须要进行校验。

内核提供了专门的函数用于访问用户空间的指针,例如:

int copy_from_user(void *to, const void __user *from, int n)

int copy_to_user(void __user *to, const void *from, int n)

创建设备文件:

应用程序想要读取或者写入设备,必须要创建设备文件。

两种方法:1、用mknod;2、自动创建。

mknod用法:mknod filename(设备文件名) type(设备文件类型) major(主设备号) minor(次设备号),例:mknod serial0 c 100 0。

设备文件一般创建在   /dev/   下,设备文件用于应用程序中访问目标设备而调用的文件,所以应用程序访问设备都是调用设备文件比如  fopen("/dev/memdev0","r+");  。

原创粉丝点击