在设备驱动程序设计中,一般会关注file 和 inode 这两个结构体。
1.file 结构体
file 结构体代表一个打开的文件,Linux系统中每一个打开的文件都有一个与之关联的 struct file 结构体;
mode_t f_mode; 文件模式
确定文件是可读的或者是可写的(或者都是), 通过位 FMODE_READ 和FMODE_WRITE.
loff_t f_pos; 文件当前的读写位置. loff_t 在所有平台都是 64 位( 在 gcc 术语里是 long long ).
unsigned int f_flags; 文件标志, 例如 O_RDONLY, O_NONBLOCK, 和 O_SYNC.
struct file_operations *f_op; 和文件关联的操作函数结构体指针.
void *private_data; private_data是一个有用的资源, 在系统调用间保留状态信息, 我们大部分例子模块都使用它.
struct dentry *f_dentry; 关联到文件的目录入口( dentry )结构.
设备驱动编写者正常地不需要关心 dentry结构, 除了作为 filp->f_dentry->d_inode 存取 inode 结构.
2.file_operation 结构
是一个字符驱动如何建立与设备编号的连接。这个结构是一个函数指针的集合,每个打开文件(内部用一个file结构来表示)与它自身的函数集合相关连(通过包含一个f_op的成员,它指向一个file_operations结构)。
struct module *owner
第一个 file_operations 成员根本不是一个操作; 它是一个指向拥有这个结构的模块的指针.
这个成员用来在它的操作还在被使用时阻止模块被卸载.
几乎所有时间中, 它被简单初始化为 THIS_MODULE, 一个在 <linux/module.h> 中定义的宏.
loff_t (*llseek) (struct file *, loff_t, int);
llseek 方法用作改变文件中的当前读/写位置, 并且新位置作为(正的)返回值.
loff_t 参数是一个"long offset", 并且就算在 32 位平台上也至少 64 位宽. 错误由一个负返回值指示.
如果这个函数指针是 NULL, seek 调用会以潜在地无法预知的方式修改 file 结构中的位置计数器( 在"file 结构" 一节中描述).
ssize_t (*read) (struct file *, char __user *, size_t, loff_t *);
用来从设备中获取数据. 在这个位置的一个空指针导致 read 系统调用以 -EINVAL("Invalid argument") 失败.
一个非负返回值代表了成功读取的字节数( 返回值是一个 "signed size" 类型, 常常是目标平台本地的整数类型).
ssize_t (*aio_read)(struct kiocb *, char __user *, size_t, loff_t);
初始化一个异步读 -- 可能在函数返回前不结束的读操作. 如果这个方法是 NULL,所有的操作会由 read 代替进行(同步地).
ssize_t (*write) (struct file *, const char __user *, size_t, loff_t *);
发送数据给设备. 如果 NULL, -EINVAL 返回给调用 write 系统调用的程序. 如果非负, 返回值代表成功写的字节数.
ssize_t (*aio_write)(struct kiocb *, const char __user *, size_t, loff_t *);
初始化设备上的一个异步写.
int (*readdir) (struct file *, void *, filldir_t);
对于设备文件这个成员应当为 NULL; 它用来读取目录, 并且仅对文件系统有用.
unsigned int (*poll) (struct file *, struct poll_table_struct *);
poll 方法是 3 个系统调用的后端: poll, epoll, 和 select, 都用作查询对一个或多个文件描述符的读或写是否会阻塞. poll 方法应当返回一个位掩码指示是否非阻塞的读或写是可能的, 并且, 可能地, 提供给内核信息用来使调用进程睡眠直到I/O 变为可能. 如果一个驱动的 poll 方法为 NULL, 设备假定为不阻塞地可读可写.
int (*ioctl) (struct inode *, struct file *, unsigned int, unsigned long);
ioctl 系统调用提供了发出设备特定命令的方法(例如格式化软盘的一个磁道, 这不是读也不是写). 另外, 几个 ioctl 命令被内核识别而不必引用 fops 表. 如果设备不提供 ioctl 方法, 对于任何未事先定义的请求(-ENOTTY, "设备无这样的ioctl"), 系统调用返回一个错误.
int (*mmap) (struct file *, struct vm_area_struct *);
mmap 用来请求将设备内存映射到进程的地址空间. 如果这个方法是 NULL, mmap 系统调用返回 -ENODEV.
int (*open)(struct inode *inode, struct file *filp);
尽管这常常是对设备文件进行的第一个操作, 不要求驱动声明一个对应的方法. 如果这个项是 NULL, 设备打开一直成功, 但是你的驱动不会得到通知.在大部分驱动中, open 应当进行下面的工作:
• 检查设备特定的错误(例如设备没准备好, 或者类似的硬件错误
• 如果它第一次打开, 初始化设备
• 如果需要, 更新 f_op 指针.
• 分配并填充要放进 filp->private_data 的任何数据结构
inode 参数有我们需要的信息,以它的 i_cdev 成员的形式, 里面包含我们之前建立的cdev 结构.
唯一的问题是通常我们不想要 cdev 结构本身, 我们需要的是包含 cdev 结构的 xxx_dev 结构体指针.
为了实现目的,内核以 container_of 宏的形式, 在 <linux/kernel.h> 中定义:
container_of(pointer, container_type, container_field);
eg:
struct scull_dev *dev; /* device information */
dev = container_of(inode->i_cdev, struct scull_dev, cdev);
filp->private_data = dev; /* for other methods */
int (*flush) (struct file *);
flush 操作在进程关闭它的设备文件描述符的拷贝时调用; 它应当执行(并且等待)设备的任何未完成的操作. 这个必须不要和用户查询请求的 fsync 操作混淆了. 当前, flush 在很少驱动中使用; SCSI 磁带驱动使用它, 例如, 为确保所有写的数据在设备关闭前写到磁带上. 如果 flush 为 NULL, 内核简单地忽略用户应用程序的请求.
int (*release) (struct inode *, struct file *);
在文件结构被释放时引用这个操作. 如同 open, release 可以为 NULL.
release 方法的角色是 open 的反面. 有时你会发现方法的实现称为 device_close, 而不是 device_release.
任一方式, 设备方法应当进行下面的任务:
• 在最后的 close 关闭设备
eg :
struct file_operations scull_fops = {
.owner = THIS_MODULE,
.llseek = scull_llseek,
.read = scull_read,
.write = scull_write,
.ioctl = scull_ioctl,
.open = scull_open,
.release = scull_release,
};
3. inode 结构体
VFS inode 结构由内核在内部用来表示文件 ,是Linux管理文件系统的最基本单位。
对于表示设备文件的inode结构体,
dev_t i_rdev; 对于代表设备文件的节点, 这个成员包含实际的设备编号.
从一个inode中获得主设备号和次设备号:
unsigned int iminor(struct inode *inode)
unsigned int imajor(struct inode *inode)
struct cdev *i_cdev; struct cdev 是内核的内部结构, 代表字符设备;
这个成员包含一个指针, 指向这个结构, 当节点指的是一个字符设备文件时.
3. udev 用户空间设备管理
将设备管理转移到用户空间,将具体的设备操作交由用户,实现了机制与策略的分离。
udev 完全在用户态工作,利用设备加入或移除时内核所发送的热插拔事件来工作。在热插拔时,设备的详细信息会由内核通过
netlink 套接字发送出来(uevent) 。udev的设备命名策略、权限控制和事件处理都是在用户态下完成的。它利用从内核收到的信息来进行创建设备文件节点等工作。
4. sysfs 文件系统
该文件系统是一个虚拟的文件系统,它可以产生一个包括所有系统硬件的层级视图,与提供进程和状态信息的proc文件系统类似。它的一个目的就是展示设备驱动模型中各组件的层次关系。
sysfs 各顶级目录:
block 目录包含所有的块设备
devices 目录包含系统所有的设备,并根据设备挂接的总线类型组织成层次结构
bus 目录包含系统中所有的总线类型
class 目录包含系统中的设备类型
在 /sys 目录下运行 tree 会看到一个相当长的树形结构。