Linux文件系统与设备文件

在设备驱动程序设计中,一般会关注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;     和文件关联的操作函数结构体指针. 

扫描二维码关注公众号,回复: 8874118 查看本文章

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.

任一方式, 设备方法应当进行下面的任务:




释放 open 分配在 filp->private_data 中的任何东西
在最后的 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 会看到一个相当长的树形结构。



发布了20 篇原创文章 · 获赞 6 · 访问量 1万+

猜你喜欢

转载自blog.csdn.net/pan337520/article/details/79971932