字符设备驱动程序
合集下载
相关主题
- 1、下载文档前请自行甄别文档内容的完整性,平台不提供额外的编辑、内容补充、找答案等附加服务。
- 2、"仅部分预览"的文档,不可在线预览部分如存在完整性等问题,可反馈申请退款(可完整预览的文档不适用该条件!)。
- 3、如文档侵犯您的权益,请联系客服反馈,我们会尽快为您处理(人工客服工作时间:9:00-18:30)。
Linux字符设备驱动程序
Linux驱动程序的分类
字符设备驱动:用于驱动能够像字节流(文件)一样被访问 的设备。应用程序通常可以利用open、close、read、write 等系统调用访问字符设备驱动。
块设备驱动:块设备和字符设备只在系统内核内部的管理上 有所区别。应用程序对于字符设备的每一个I/O操作都会被 内核直接传递给对应的驱动程序;而应用程序对于块设备的 操作要经过虚拟文件系统(VFS)和缓冲区管理系统间接地 传递给驱动程序处理。
当前读/写位置
unsigned int f_flags
标识文件打开时,是否可读或可写 O_RDONLY O_NONBLOCK O_SYNC
struct file_operations *f_op
文件相关的操作,指向所实现的struct file_operations
void *private_data: 私有数据指针。驱动程序可以将这个字段用于任何目的或者忽略这个字段。
字符设备驱动程序基本结构
注册设备 ,在模块或驱动初始化时调用
Linux-2.4 及之前
int register_chrdev(unsigned int major, const char *name, struct file_operations *fops)
如何操作字符设 备的接口
Linux-2.6 void cdev_init( struct cdev *, struc t file_operations *); int cdev_add(st ruct cdev *, dev_t, unsigned) ;
所请求的连续设 备编号的个数
和该编号范围关 联的设备名称
主设备号与次设备号
动态分配主设备号:
输出的设备号 #include <linux/fs.h> int alloc_chrdev_resion(dev_t *dev,unsigned int firstminor,
unsigned int count,char *name);
字符设备驱动程序基本结构
注销设备:在模块卸载时调用
Linux-2.4及之前 int unregister_chrdev(unsigned int major,
const char *name);
Linux-2.6 void cdev_del (struct cdev *);
字符设备驱动程序基本结构
主设备号与次设备号
分配主设备号
手工分配主设备号:找一个内核没有使用的主设备号来使用。
#include <linux/fs.h> int register_chrdev_region( dev_t first, unsigned int count, char *name );
要分配的设备编 号范围的起始值, 次设备号经常为0
对表示设备文件的inode结构,该字段包含了真正的设备编号。
struct cdev *i_cdev;
struct cdev是表示字符设备的内核的内部结构。 当inode指向一个字符设备文件时,该字段包含了指向struct cdev结构的指针
从一个inode中获得主设备号和次设备号:
unsigned int iminor(struct inode *inode); unsigned int imajor(struct inode *inode);
Inode与file的区别:file表示打开的文件描述符,多个file结构, 可以指向单个inode结构。
struct inode { dev_t i_rdev; struct cdev *i_cdev; // ………………………
};
Inode结构体
Inode结构中的两个主要字段:
dev_t i_rdev;
inode结构体
内核用inode结构在内部表示文件,用于存储文件访问权限、 属主、组、大小、生产时间等VFS关心的信息。
其字段中我们只关心i_rdev(设备号),和i_cdev(和该文件所 对应的cdev结构)
我们在创建设备文件时,内核会自动创建一个对应的inode结 构体,并将其i_cdev字段指向对应的字符设备结构体cdev(事 先已经在内核中注册过)。
/ /设备驱动模块加载函数 static int __init xxx_init(void) {
... cdev_init(&xxx_dev.cdev, &xxx_fops); / /初始化cdev xxx_dev.cdev.owner = THIS_MODULE; / /获取字符设备号 if (xxx_major) {
应用程序如何访问设备
fd1 = open(“/dev/ttyS1”, O_RDWR); // 阻塞 fd2 = open(“/dev/ttyS1”, O_RDWR | O_NONBLOCK); // 非阻塞 int read(int fd, const void *buf, size_t length); int write(int fd, const void *buf, size_t length); int lseek(int fd, offset_t offset, int whence); int ioctl( int fd, int cmd, void *arg); int close(int fd);
{
struct kobject kobj;
/* 内嵌的kobject 对象 */
struct module *owner; /*所属模块*/
struct file_operations *ops; /*文件操作结构体*/
struct list_head list; dev_t dev;
/*设备号*/
创建设备文件节点
设备文件与设备号
为了体现“一切都是文件”的设计思想,linux将每个已安装 的设备都表示为一个设备文件。
设备文件通常位于/dev子目录。 对于字符设备,应用程序可以利用open、close、read、
write等系统调用访问其设备文件,这些I/O操作都被直接传递 给该设备文件所对应的设备。 每个设备文件中都存储了该设备的“主设备号”和“次设备 号”。 一般由同一个内核模块管理的多个设备占用同一个主设备号, 具体设备用次设备号标识。 用mknod filename c major minor命令创建设备文件 用rm filename命令删除设备文件。注意删除设备文件并不会 影响驱动模块。
file operations的初始化
struct file_operations my_fops = { .owner = THIS_MODULE, .llseek = my_llseek, .read = my_read, .write = my_write, .ioctl = my_ioctl, .open = my_open, .release = my_release,
unsigned int count;
};
cdev的kobj、 list 、 count字段不用我们关系和维护(内核代 劳),我们只需将其ops字段指向为我们自己的file operations 结构。
对cdev结构体的操作
操作cdev的函数
void cdev_init( struct cdev *, struc t file_operations *);
};
file_operations 结构体
file_operations的主要成员:
struct module *owner: 指向模块自身 open:打开设备 release:关闭设备 read:从设备上读数据 write:向设备上写数据 ioctl:I/O控制函数 llseek:定位读写指针 mmap:映射设备空间到进程的地址空间
网络设备驱动:应用程序必须利用套接字(socket)接口访问 网络设备。
网络设备驱动程序
字符设备驱动程序基本结构
字符设备开发的基本步骤
确定主设备号和次设备号 实现字符驱动程序
实现file_operations结构体 构造字符设备结构体cdev 在模块加载函数中注册字符设备 在模块卸载函数中注销字符设备
f_flags;
fmode_t
f_mode;
loff_t
f_pos;
struct dentry
*f_dentry
void*
private_data;
};
file结构体
file 结构体
file结构:
file_operations结构相关的一个结构体。 描述一个正在打开的设备文件。
成员:
loff_t f_pos:
完成字符设备的注销,通常
在模块的卸载函数中调用 分别向系统添加一个cdev,
void cdev_del(struct cdev *); 完成字符设备的注册,通常
在模块加载函数中调用
file_operations 结构体
file_operations 结构体
字符驱动和内核的接口: 在include/linux/fs.h定义
register_chrdev_region(xxx_dev_no, 1, DEV_NAME); } else {
alloc_chrdev_region(&xxx_dev_no, 0, 1, DEV_NAME); } ret = cdev_add(&xxx_dev.cdev, xxx_dev_no, 1 ) ; / /注册设备 ... }
字符驱动只要实现一个file_operations结构体并注册到内核中, 内核就有了操作此设备的能力。
struct file_operations { struct module *owner; loff_t (*llseek) (struct file *, loff_t, int); 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 *); int (*ioctl) (struct inode *, struct file *, unsigned int, unsigned long); int (*open) (struct inode *, struct file *); int (*flush) (struct file *, fl_owner_t id); int (*release) (struct inode *, struct file *); // ………………………
struct cdev *cdev_allwenku.baidu.comc(void) ;
用于初始化cdev的成员, 并建立cdev和
函数用于动态申请一个fcildee_vop内e存rations之间的连接
int cdev_add(st ruct cdev *, dev_t, unsigned) ; 分别向系统删除一个cdev,
};
file结构体
file结构代表一个打开的文件,它由内核在应用程序 open时创建,并将该文件所对应的file operations记 录在file结构中。
在应用程序调用close函数,内核会释放该数据结构。
struct file {
struct file_operations
*f_op;
unsigned int
设备号的内部表达
设备编号的内部表达
dev_t类型(32位): 用来保存设备编号(包括主设备号(12位)和次设备 号(20位))
从dev_t获得主设备号和次设备号: MAJOR(dev_t); MINOR(dev_t);
将主设备号和次设备号转换成dev_t类型: MKDEV(int major,int minor);
要使用的被请求的 第一个次设备号
主设备号与次设备号
释放设备号
void unregister_chrdev_region(dev_t first, unsigned int count);
通常在模块的清 除函数中调用。
记录字符设备的结构体cdev
实现字符驱动程序
cdev 结构体
struct cdev
Linux驱动程序的分类
字符设备驱动:用于驱动能够像字节流(文件)一样被访问 的设备。应用程序通常可以利用open、close、read、write 等系统调用访问字符设备驱动。
块设备驱动:块设备和字符设备只在系统内核内部的管理上 有所区别。应用程序对于字符设备的每一个I/O操作都会被 内核直接传递给对应的驱动程序;而应用程序对于块设备的 操作要经过虚拟文件系统(VFS)和缓冲区管理系统间接地 传递给驱动程序处理。
当前读/写位置
unsigned int f_flags
标识文件打开时,是否可读或可写 O_RDONLY O_NONBLOCK O_SYNC
struct file_operations *f_op
文件相关的操作,指向所实现的struct file_operations
void *private_data: 私有数据指针。驱动程序可以将这个字段用于任何目的或者忽略这个字段。
字符设备驱动程序基本结构
注册设备 ,在模块或驱动初始化时调用
Linux-2.4 及之前
int register_chrdev(unsigned int major, const char *name, struct file_operations *fops)
如何操作字符设 备的接口
Linux-2.6 void cdev_init( struct cdev *, struc t file_operations *); int cdev_add(st ruct cdev *, dev_t, unsigned) ;
所请求的连续设 备编号的个数
和该编号范围关 联的设备名称
主设备号与次设备号
动态分配主设备号:
输出的设备号 #include <linux/fs.h> int alloc_chrdev_resion(dev_t *dev,unsigned int firstminor,
unsigned int count,char *name);
字符设备驱动程序基本结构
注销设备:在模块卸载时调用
Linux-2.4及之前 int unregister_chrdev(unsigned int major,
const char *name);
Linux-2.6 void cdev_del (struct cdev *);
字符设备驱动程序基本结构
主设备号与次设备号
分配主设备号
手工分配主设备号:找一个内核没有使用的主设备号来使用。
#include <linux/fs.h> int register_chrdev_region( dev_t first, unsigned int count, char *name );
要分配的设备编 号范围的起始值, 次设备号经常为0
对表示设备文件的inode结构,该字段包含了真正的设备编号。
struct cdev *i_cdev;
struct cdev是表示字符设备的内核的内部结构。 当inode指向一个字符设备文件时,该字段包含了指向struct cdev结构的指针
从一个inode中获得主设备号和次设备号:
unsigned int iminor(struct inode *inode); unsigned int imajor(struct inode *inode);
Inode与file的区别:file表示打开的文件描述符,多个file结构, 可以指向单个inode结构。
struct inode { dev_t i_rdev; struct cdev *i_cdev; // ………………………
};
Inode结构体
Inode结构中的两个主要字段:
dev_t i_rdev;
inode结构体
内核用inode结构在内部表示文件,用于存储文件访问权限、 属主、组、大小、生产时间等VFS关心的信息。
其字段中我们只关心i_rdev(设备号),和i_cdev(和该文件所 对应的cdev结构)
我们在创建设备文件时,内核会自动创建一个对应的inode结 构体,并将其i_cdev字段指向对应的字符设备结构体cdev(事 先已经在内核中注册过)。
/ /设备驱动模块加载函数 static int __init xxx_init(void) {
... cdev_init(&xxx_dev.cdev, &xxx_fops); / /初始化cdev xxx_dev.cdev.owner = THIS_MODULE; / /获取字符设备号 if (xxx_major) {
应用程序如何访问设备
fd1 = open(“/dev/ttyS1”, O_RDWR); // 阻塞 fd2 = open(“/dev/ttyS1”, O_RDWR | O_NONBLOCK); // 非阻塞 int read(int fd, const void *buf, size_t length); int write(int fd, const void *buf, size_t length); int lseek(int fd, offset_t offset, int whence); int ioctl( int fd, int cmd, void *arg); int close(int fd);
{
struct kobject kobj;
/* 内嵌的kobject 对象 */
struct module *owner; /*所属模块*/
struct file_operations *ops; /*文件操作结构体*/
struct list_head list; dev_t dev;
/*设备号*/
创建设备文件节点
设备文件与设备号
为了体现“一切都是文件”的设计思想,linux将每个已安装 的设备都表示为一个设备文件。
设备文件通常位于/dev子目录。 对于字符设备,应用程序可以利用open、close、read、
write等系统调用访问其设备文件,这些I/O操作都被直接传递 给该设备文件所对应的设备。 每个设备文件中都存储了该设备的“主设备号”和“次设备 号”。 一般由同一个内核模块管理的多个设备占用同一个主设备号, 具体设备用次设备号标识。 用mknod filename c major minor命令创建设备文件 用rm filename命令删除设备文件。注意删除设备文件并不会 影响驱动模块。
file operations的初始化
struct file_operations my_fops = { .owner = THIS_MODULE, .llseek = my_llseek, .read = my_read, .write = my_write, .ioctl = my_ioctl, .open = my_open, .release = my_release,
unsigned int count;
};
cdev的kobj、 list 、 count字段不用我们关系和维护(内核代 劳),我们只需将其ops字段指向为我们自己的file operations 结构。
对cdev结构体的操作
操作cdev的函数
void cdev_init( struct cdev *, struc t file_operations *);
};
file_operations 结构体
file_operations的主要成员:
struct module *owner: 指向模块自身 open:打开设备 release:关闭设备 read:从设备上读数据 write:向设备上写数据 ioctl:I/O控制函数 llseek:定位读写指针 mmap:映射设备空间到进程的地址空间
网络设备驱动:应用程序必须利用套接字(socket)接口访问 网络设备。
网络设备驱动程序
字符设备驱动程序基本结构
字符设备开发的基本步骤
确定主设备号和次设备号 实现字符驱动程序
实现file_operations结构体 构造字符设备结构体cdev 在模块加载函数中注册字符设备 在模块卸载函数中注销字符设备
f_flags;
fmode_t
f_mode;
loff_t
f_pos;
struct dentry
*f_dentry
void*
private_data;
};
file结构体
file 结构体
file结构:
file_operations结构相关的一个结构体。 描述一个正在打开的设备文件。
成员:
loff_t f_pos:
完成字符设备的注销,通常
在模块的卸载函数中调用 分别向系统添加一个cdev,
void cdev_del(struct cdev *); 完成字符设备的注册,通常
在模块加载函数中调用
file_operations 结构体
file_operations 结构体
字符驱动和内核的接口: 在include/linux/fs.h定义
register_chrdev_region(xxx_dev_no, 1, DEV_NAME); } else {
alloc_chrdev_region(&xxx_dev_no, 0, 1, DEV_NAME); } ret = cdev_add(&xxx_dev.cdev, xxx_dev_no, 1 ) ; / /注册设备 ... }
字符驱动只要实现一个file_operations结构体并注册到内核中, 内核就有了操作此设备的能力。
struct file_operations { struct module *owner; loff_t (*llseek) (struct file *, loff_t, int); 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 *); int (*ioctl) (struct inode *, struct file *, unsigned int, unsigned long); int (*open) (struct inode *, struct file *); int (*flush) (struct file *, fl_owner_t id); int (*release) (struct inode *, struct file *); // ………………………
struct cdev *cdev_allwenku.baidu.comc(void) ;
用于初始化cdev的成员, 并建立cdev和
函数用于动态申请一个fcildee_vop内e存rations之间的连接
int cdev_add(st ruct cdev *, dev_t, unsigned) ; 分别向系统删除一个cdev,
};
file结构体
file结构代表一个打开的文件,它由内核在应用程序 open时创建,并将该文件所对应的file operations记 录在file结构中。
在应用程序调用close函数,内核会释放该数据结构。
struct file {
struct file_operations
*f_op;
unsigned int
设备号的内部表达
设备编号的内部表达
dev_t类型(32位): 用来保存设备编号(包括主设备号(12位)和次设备 号(20位))
从dev_t获得主设备号和次设备号: MAJOR(dev_t); MINOR(dev_t);
将主设备号和次设备号转换成dev_t类型: MKDEV(int major,int minor);
要使用的被请求的 第一个次设备号
主设备号与次设备号
释放设备号
void unregister_chrdev_region(dev_t first, unsigned int count);
通常在模块的清 除函数中调用。
记录字符设备的结构体cdev
实现字符驱动程序
cdev 结构体
struct cdev