Linux 将系统设备分为:字符设备、块设备、网络设备。
字符设备是 Linux 驱动中最基本的一类设备驱动,字符设备就是一个一个字节,
按照字节流进行读写操作的设备,读写数据是分先后顺序的。
在Linux的世界里面一切皆文件,所有的硬件设备操作到应用层都会被抽象成文件
的操作。我们知道如果应用层要访问硬件设备,它必定要调用到硬件对应的驱动程序。
1.在Linux文件系统中,每个文件都用一个struct inode结构体来描述,
这个结构体里面记录了 这个文件的所有信息,例如:文件类型,访问权限等。
struct inode信息
2.在Linux操作系统中,每个驱动程序在应用层的/dev目录下都会有一个
设备文件和它对应,并且该文件会有对应的主设备号和次设备号。
3.在Linux操作系统中,每个驱动程序都要分配一个主设备号,字符设备的设备号
保存在struct cdev结构体中。
struct cdev {
struct kobject kobj;
struct module *owner;
const struct file_operations *ops;//接口函数集合
struct list_head list;//内核链表
dev_t dev; //设备号
unsigned int count;//次设备号个数
};
4.在Linux操作系统中,每打开一次文件,Linux操作系统在VFS层都会分配一个struct file
结构体来描述打开的这个文件。该结构体用于维护文件打开权限、文件指针偏移值、私有
内存地址等信息。(strcut file有两个非常重要的字段:文件描述符和缓冲区)
内核共提供了三个函数来注册一组字符设备编号,这三个函数分别是
register_chrdev_region()、alloc_chrdev_region()和 register_chrdev()。
注意事项
1.使用register_chrdev注册字符设备,其内部申请struct cdev 结构,
并调用cdev_add函数添加设备。
2.使用register_chrdev_region/alloc_chrdev_region注册字符设备,需要在外部事先
定义struct cdev 结构,然后使用函数cdev_init初始化它,最后还需在外部调用cdev_add
函数添加设备。
//比较老的内核注册的形式 早期的驱动
static inline int register_chrdev(unsigned int major, const char *name,
const struct file_operations *fops)
功能:
注册或者分配设备号,并注册fops到cdev结构体,
如果major>0,功能为注册该主设备号,
如果major=0,功能为动态分配主设备号。
参数:
@major : 主设备号
@name : 设备名称,执行 cat /proc/devices显示的名称
@fops : 文件系统的接口指针
返回值
如果major>0 成功返回0,失败返回负的错误码
如果major=0 成功返回主设备号,失败返回负的错误码
int register_chrdev_region(dev_t from, unsigned count, const char *name)
功能:
注册一个范围()的设备号
参数:
@from 设备号
@count 注册的设备个数
@name 设备的名字
返回值:
成功返回0,失败返回错误码(负数)
int alloc_chrdev_region(dev_t *dev, unsigned baseminor, unsigned count,
const char *name)
功能:
注册一个主设备号由内核动态分配,次设备号为baseminor~baseminor+count的设备驱动
参数:
@dev: 用来获取设备号
@baseminor:次设备号起始值
@count: 次设备号个数
@name: 设备名称
返回值:
成功返回0,失败返回错误码(负数)
//注销
static inline void unregister_chrdev(unsigned int major, const char *name)
void cdev_init(struct cdev *cdev, const struct file_operations *fops)
功能:
初始化cdev结构体
参数:
@cdev cdev结构体地址
@fops 操作字符设备的函数接口地址
返回值:
无
int cdev_add(struct cdev *p, dev_t dev, unsigned count)
功能:
添加一个字符设备到操作系统
参数:
@p cdev结构体地址
@dev 设备号
@count 次设备号个数
返回值:
成功返回0,失败返回错误码(负数)
void cdev_del(struct cdev *p)
功能:
从系统中删除一个字符设备
参数:
@p cdev结构体地址
返回值:
无
struct file_operations {
struct module *owner;//拥有该结构的模块的指针,一般为THIS_MODULES
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 *);//向设备发送数据
ssize_t (*aio_read) (struct kiocb *, const struct iovec *, unsigned long, loff_t);//初始化一个异步的读取操作
ssize_t (*aio_write) (struct kiocb *, const struct iovec *, unsigned long, loff_t);//初始化一个异步的写入操作
int (*readdir) (struct file *, void *, filldir_t);//仅用于读取目录,对于设备文件,该字段为NULL
unsigned int (*poll) (struct file *, struct poll_table_struct *); //轮询函数,判断目前是否可以进行非阻塞的读写或写入
int (*ioctl) (struct inode *, struct file *, unsigned int, unsigned long); //执行设备I/O控制命令
long (*unlocked_ioctl) (struct file *, unsigned int, unsigned long); //不使用BLK文件系统,将使用此种函数指针代替ioctl
long (*compat_ioctl) (struct file *, unsigned int, unsigned long); //在64位系统上,32位的ioctl调用将使用此函数指针代替
int (*mmap) (struct file *, struct vm_area_struct *); //用于请求将设备内存映射到进程地址空间
int (*open) (struct inode *, struct file *); //打开
int (*flush) (struct file *, fl_owner_t id);
int (*release) (struct inode *, struct file *); //关闭
int (*fsync) (struct file *, struct dentry *, int datasync); //刷新待处理的数据
int (*aio_fsync) (struct kiocb *, int datasync); //异步刷新待处理的数据
int (*fasync) (int, struct file *, int); //通知设备FASYNC标志发生变化
int (*lock) (struct file *, int, struct file_lock *);
ssize_t (*sendpage) (struct file *, struct page *, int, size_t, loff_t *, int);
unsigned long (*get_unmapped_area)(struct file *, unsigned long, unsigned long, unsigned long, unsigned long);
int (*check_flags)(int);
int (*flock) (struct file *, int, struct file_lock *);
ssize_t (*splice_write)(struct pipe_inode_info *, struct file *, loff_t *, size_t, unsigned int);
ssize_t (*splice_read)(struct file *, loff_t *, struct pipe_inode_info *, size_t, unsigned int);
int (*setlease)(struct file *, long, struct file_lock **);
};
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
/* 1. 确定主设备号 */
static int major;
static int hello_open(struct inode *inode, struct file *file)
{
printk("hello_open\n");
return 0;
}
static int hello2_open(struct inode *inode, struct file *file)
{
printk("hello2_open\n");
return 0;
}
/* 2. 构造file_operations */
static struct file_operations hello_fops = {
.owner = THIS_MODULE,
.open = hello_open,
};
static struct file_operations hello2_fops = {
.owner = THIS_MODULE,
.open = hello2_open,
};
#define HELLO_CNT 2
static struct cdev hello_cdev;
static struct cdev hello2_cdev;
static struct class *cls;
static int hello_init(void)
{
dev_t devid;
/* 3. 告诉内核 */
#if 0
major = register_chrdev(0, "hello", &hello_fops); /* (major, 0), (major, 1), ..., (major, 255)都对应hello_fops */
#else
if (major) {
// 事先知道可用的主设备号,和起始次设备号
devid = MKDEV(major, 0);
register_chrdev_region(devid, HELLO_CNT, "hello"); /* (major,0~1) 对应 hello_fops, (major, 2~255)都不对应hello_fops */
} else {
// 事先不知道可用的主设备号,由内核动态分配
alloc_chrdev_region(&devid, 0, HELLO_CNT, "hello"); /* (major,0~1) 对应 hello_fops, (major, 2~255)都不对应hello_fops */
major = MAJOR(devid);
}
cdev_init(&hello_cdev, &hello_fops);
cdev_add(&hello_cdev, devid, HELLO_CNT);
devid = MKDEV(major, 2);
register_chrdev_region(devid, 1, "hello2");
cdev_init(&hello2_cdev, &hello2_fops);
cdev_add(&hello2_cdev, devid, 1);
#endif
cls = class_create(THIS_MODULE, "hello");
// 使用register_chrdev注册,下面的四个设备节点都将对应该设备驱动,都能调用hello_open
// 使用 register_chrdev_region/alloc_chrdev_region (60/63)注册,设备节点/dev/hello0、/dev/hello1对应hello_fops设备驱动,调用hello_open打开
// 使用 register_chrdev_region/alloc_chrdev_region (71)注册,设备节点/dev/hello2对应hello2_fops设备驱动,调用hello2_open打开
// /dev/hello3节点未注册到设备驱动,无法打开设备。
class_device_create(cls, NULL, MKDEV(major, 0), NULL, "hello0"); /* /dev/hello0 */
class_device_create(cls, NULL, MKDEV(major, 1), NULL, "hello1"); /* /dev/hello1 */
class_device_create(cls, NULL, MKDEV(major, 2), NULL, "hello2"); /* /dev/hello2 */
class_device_create(cls, NULL, MKDEV(major, 3), NULL, "hello3"); /* /dev/hello3 */
return 0;
}
static void hello_exit(void)
{
class_device_destroy(cls, MKDEV(major, 0));
class_device_destroy(cls, MKDEV(major, 1));
class_device_destroy(cls, MKDEV(major, 2));
class_device_destroy(cls, MKDEV(major, 3));
class_destroy(cls);
cdev_del(&hello_cdev);
unregister_chrdev_region(MKDEV(major, 0), HELLO_CNT);
cdev_del(&hello2_cdev);
unregister_chrdev_region(MKDEV(major, 2), 1);
}
module_init(hello_init);
module_exit(hello_exit);
MODULE_LICENSE("GPL");