内核学习15-字符设备驱动详解

linux 内核学习15-字符设备驱动详解

1. 字符设备驱动的抽象

这里将上面学习的内容进行解析

<include/linux/cdev.h>

struct cdev{
    struct kobject kobj;
    struct module *owner;
    const struct file_operations *ops;
    struct list_head list;
    dev_t dev;
    unsigned int count;
}

参数含义
kobj用于Linux设备驱动模型
owner字符设备驱动所在的内核模块对象指针程序
ops字符设备驱动程序中最关键的一个操作函数,在和应用程序交互过程中起到了桥梁枢纽的作用
kobjlist用来将字符设备串成一个链表
dev字符设号备的设备号,由 主设备号和 次设备号组成
count同属于一个主设备号的次设备号的个数

主设备号和次设备号通常可以通过如下宏来获取,也就是高12比特位是主设备号,低20比特位是次设备号

#define MINORBITS   20
#define MINORMASK  ((1U<<MINORBITS)  -1)

#define MAJOR(dev)  ((unsigned int ) ((dev)>>MINORBITS))
#define MINOR(dev)  ((unsigned int ) ((dev)& MINORMASK))

#define MKDEV(ma,mi)   (((ma)<<MINORBITS  |(mi)))

设备驱动程序可以由两种方式来产生struct cdev,一种是使用全局静态变量,另一种是使用内核提供的cdev_alloc()接口函数。

static struct cdev mydemo_cdev;

struct mydemo_cdev =cdev_alloc();

初次之外,Linux内核还提供了若干个与cdev相关的API函数。

  1. cdev_init()函数,初始化cdev数据结构,并且建立该设备与驱动操作方法集,file_operations之间的连接关系。
void cdev_init(struct cdev *cdev,const struct file_operations *fops)
  1. cdev_add()函数,把一个字符设备添加到系统中,通常在驱动程序的probe函数里会调用该接口来注册一个字符设备。
int cdev_add(struct cdev *p,dev_t devd,unsigned count)
参数含义
p表示一个设备的cdev数据结构
dev表示设备的设备号
count表示这个 主设备号里可以由多少个次设备号,通常同一个主设备号可以由多个次设备号不相同的设备,如系统中同时有多个串口,他们都是名为tty的设备,主设备都是4
  1. cdev_del()函数,从系统中删除一个cdev,通常在驱动程序的卸载函数里会调用该接口
void cdev_del(struct cdev *p)

2. 设备号的管理

字符这杯驱动的初始化函数(probe函数)有一个很重要的工作,即为设备分配设备号。设备号是系统中珍贵的资源,内核必须避免发生两个设备驱动使用同一个主设备号的情况,因此在编写驱动程序要小心。Linux内核提供两个接口函数完成设备的申请。

int register_chrdev_region(dev_t from,unsigned count,const char *name)

register_chrdev_region()函数需要指定主设备号,可以连续分配多个。也就是说,在使用该函数之前,驱动程序编写者必须保证分配的主设备号在系统中没有使用。内核文档documentation/devices.txt文件描述了系统中已经分配出去的设备号,因此使用该接口函数的程序员应该事先约定文档,避免使用已经被系统占用的主设备号。

int alloc_chrdev_region(dev_t *dev,unsigned baseminor,unsigned count,const char *name)

alloc_chrdev_region()函数会自动分配一个主设备号,可以避免和系统占用的主设备号重复,建议驱动开发者使用这个函数来分配主设备号。
在驱动程序的卸载函数中需要把主设备号释放给系统,可以调用如下接口函数。

void unregister_chrdev_region(dev_t from,unsigned count)

3. 设备结点

在Linux系统中有一个原则,就是万物皆文件。设备结点也算是一个特殊的文件,称之为设备文件,是连接内核驱动空间和用户空间的桥梁。
主设备号:代表一类设备
次设备号:代表同一类设备的不同个体,每个次设备号都有一个不同的设备结点。

按照Linux的习惯,系统中所有的设备结点都存放在/dev/ 目录中,dev目录是一个动态生成的,使用devtmpfs虚拟文件系统挂在的、基于RAM的虚拟文件系统。

/mnt # ls -l /dev/


total 0
crw-rw----    1 0        0          14,   4 Sep  2 10:07 audio
crw-rw----    1 0        0           5,   1 Sep  2 10:07 console
crw-rw----    1 0        0          10,  63 Sep  2 10:07 cpu_dma_latency
crw-r--r--    1 0        0         252,   0 Sep  2 10:14 demo_drv

第一列中c表示字符设备,d表示块设备,后面还会有更多的主设备号和次设备号。

设备结点的生成方式有两种方式:一种是使用mknod命令手工生成,另一个使用udev机制动态生成。

mknod filename type major minor

举个例子

mkdir -p  /dev/cobing
mknod /dev/cobing/mydev1 c 128 512

4. 字符设备操作方法集

在mydemo例子中,实现了一个demodrv_fops的操作方法集,里面包含open,release,read和write等方法。从C语言的角度,就是抽象和定义了一堆函数指针,这些函数指针称为file_operations方法,是在Linux内核发展过程中不断扩充壮大的。

static const struct file_operations demodrv_fops={
    .owner=THIS_MODULE,
    .open=demodrv_open,
    .release=demodrv_release,
    .read=demodrv_read,
    .write=demodrv_write
};


这个方法是通过cdev_init()函数和设备监理的一个连接关系,因此在用户空间的test程序中,直接使用open()函数打开这个设备结点。

#define DEMO_DEV_NAME "/dev/demo_drv"
fd=open(DEMO_DEV_NAME,O_RDONLY);

open函数的第一个参数是设备文件名,第二个参数用来指定打开文件的属性。open函数执行成功会返回一个文件描述符,俗称文件句柄否则返回-1.
应用程序的open函数执行时,会通过系统调用进入内核空间,在内核空间的虚拟文件系统层(VFS)经过复杂的转换,最后调用设备驱动的file_operations方法集中的open方法。因此,驱动开发者有必要了解file_operations结构体的组成,该结构体定义在include/linux/fs.h头文件中,字符设备驱动程序的核心开发工作是实现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 *);
	ssize_t (*read_iter) (struct kiocb *, struct iov_iter *);
	ssize_t (*write_iter) (struct kiocb *, struct iov_iter *);
	int (*iterate) (struct file *, struct dir_context *);
	int (*iterate_shared) (struct file *, struct dir_context *);
	__poll_t (*poll) (struct file *, struct poll_table_struct *);
	long (*unlocked_ioctl) (struct file *, unsigned int, unsigned long);
	long (*compat_ioctl) (struct file *, unsigned int, unsigned long);
	int (*mmap) (struct file *, struct vm_area_struct *);
	unsigned long mmap_supported_flags;
	int (*open) (struct inode *, struct file *);
	int (*flush) (struct file *, fl_owner_t id);
	int (*release) (struct inode *, struct file *);
	int (*fsync) (struct file *, loff_t, loff_t, int datasync);
	int (*fasync) (int, struct file *, int);
	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 **, void **);
	long (*fallocate)(struct file *file, int mode, loff_t offset,
			  loff_t len);
	void (*show_fdinfo)(struct seq_file *m, struct file *f);
#ifndef CONFIG_MMU
	unsigned (*mmap_capabilities)(struct file *);
#endif
	ssize_t (*copy_file_range)(struct file *, loff_t, struct file *,
			loff_t, size_t, unsigned int);
	int (*clone_file_range)(struct file *, loff_t, struct file *, loff_t,
			u64);
	int (*dedupe_file_range)(struct file *, loff_t, struct file *, loff_t,
			u64);
	int (*fadvise)(struct file *, loff_t, loff_t, int);
} __randomize_layout;
参数含义
llseek修改文件的当前读写位置,并返回新位置
read从设备驱动中读取数据到用户空间,函数返回成功读取的字节数,如果返回负数,则说明读取失败
write把用户空间的数据写入设备中,函数返回成功写入的字节数
poll查询设备是否可以立即读写,该方法主要用于阻塞型I?O
unlocked_ioctl和compat_ioctl提供与设备相关的控制命令的实现
mmap设备内存射到进程的虚拟地址中
open打开设备
release关闭设备
aio_read和aio_write所谓的异步I/O就是提交完I/O请求之后立即返回,不需要等到I/O操作完成之后再去做别的事情,因此具有非阻塞特性。设备驱动完成I/O操作之后,可以通过发送信号或者回调函数等方式来通知
fsync实现一种称为异步通知的方法
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值