您的位置:首页 > 运维架构

关于file_operations结构体

2017-11-21 13:46 253 查看
结构体file_operations在头文件
linux/fs.h中定义,用来存储驱动内核模块提供的对 设备进行各种操作的函数的指针。该结构体的每个域都对应着驱动内核模块用来处理某个被请求的 事务的函数的地址。

举个例子,每个字符设备需要定义一个用来读取设备数据的函数。结构体
file_operations中存储着内核模块中执行这项操作的函数的地址。一下是该结构体 在内核2.6.5中看起来的样子:

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(*aio_read) (struct kiocb *, char __user *, size_t, loff_t);
ssize_t(*write) (struct file *, const char __user *, size_t, loff_t *);
ssize_t(*aio_write) (struct kiocb *, const char __user *, size_t,
loff_t);
int (*readdir) (struct file *, void *, filldir_t);
unsigned int (*poll) (struct file *, struct poll_table_struct *);
int (*ioctl) (struct inode *, struct file *, unsigned int,
unsigned long);
int (*mmap) (struct file *, struct vm_area_struct *);
int (*open) (struct inode *, struct file *);
int (*flush) (struct file *);
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);
int (*lock) (struct file *, int, struct file_lock *);
ssize_t(*readv) (struct file *, const struct iovec *, unsigned long,
loff_t *);
ssize_t(*writev) (struct file *, const struct iovec *, unsigned long,
loff_t *);
ssize_t(*sendfile) (struct file *, loff_t *, size_t, read_actor_t,
void __user *);
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);
};

驱动内核模块是不需要实现每个函数的。像视频卡的驱动就不需要从目录的结构 中读取数据。那么,相对应的file_operations重的项就为
NULL。

gcc还有一个方便使用这种结构体的扩展。你会在较现代的驱动内核模块中见到。 新的使用这种结构体的方式如下:

struct file_operations fops = {
read: device_read,
write: device_write,
open: device_open,
release: device_release
};

同样也有C99语法的使用该结构体的方法,并且它比GNU扩展更受推荐。我使用的版本为
2.95为了方便那些想移植你的代码的人,你最好使用这种语法。它将提高代码的兼容性:

struct file_operations fops = {
.read = device_read,
.write = device_write,
.open = device_open,
.release = device_release
};

这种语法很清晰,你也必须清楚的意识到没有显示声明的结构体成员都被gcc初始化为NULL。

指向结构体struct file_operations的指针通常命名为fops。

关于file结构体

每一个设备文件都代表着内核中的一个file结构体。该结构体在头文件linux/fs.h定义。注意,file结构体是内核空间的结构体, 这意味着它不会在用户程序的代码中出现。它绝对不是在glibc中定义的FILE。
FILE自己也从不在内核空间的函数中出现。它的名字确实挺让人迷惑的。 它代表着一个抽象的打开的文件,但不是那种在磁盘上用结构体
inode表示的文件。

指向结构体struct file的指针通常命名为
filp
。 你同样可以看到struct file file的表达方式,但不要被它诱惑。

去看看结构体
file
的定义。大部分的函数入口,像结构体
struct dentry
没有被设备驱动模块使用,你大可忽略它们。这是因为设备驱动模块并不自己直接填充结构体
file:它们只是使用在别处建立的结构体file中的数据。

注册一个设备

如同先前讨论的,字符设备通常通过在路径/dev[1]设备文
件访问。主设备号告诉你哪些驱动模块是用来操纵哪些硬件设备的。从设备号是驱动模 块自己使用来区别它操纵的不同设备,当此驱动模块操纵不只一个设备时。

将内核驱动模块加载入内核意味着要向内核注册自己。这个工作是和驱动模块获 得主设备号时初始化一同进行的。你可以使用头文件
linux/fs.h中的函数register_chrdev来实现。

int register_chrdev(unsigned int major, const char *name, struct file_operations *fops);

其中unsigned int major是你申请的主设备号,
const char *name是将要在文件/proc/devices
struct file_operations *fops是指向你的驱动模块的
file_operations表的指针。负的返回值意味着注册失败。 注意注册并不需要提供从设备号。内核本身并不在意从设备号。

现在的问题是你如何申请到一个没有被使用的主设备号?最简单的方法是查看文件
Documentation/devices.txt从中挑选一个没有被使用的。这不是 一劳永逸的方法因为你无法得知该主设备号在将来会被占用。最终的方法是让内核为你动 态分配一个。

如果你向函数
register_chrdev
传递为0的主设备号,那么 返回的就是动态分配的主设备号。副作用就是既然你无法得知主设备号,你就无法预先建 立一个设备文件。 有多种解决方法。第一种方法是新注册的驱动模块会输出自己新分配
到的主设备号,所以我们可以手工建立需要的设备文件。第二种是利用文件 /proc/devices新注册的驱动模块的入口,要么手工建立设备文件, 要么编一个脚本去自动读取该文件并且生成设备文件。第三种是在我们的模块中,当注册 成功时,使用
mknod
统调用建立设备文件并且调用
rm 删除该设备 文件在驱动模块调用函数
cleanup_module
前。

注销一个设备

即使时root也不能允许随意卸载内核模块。当一个进程已经打开一个设备文件时我 们卸载了该设备文件使用的内核模块,我们此时再对该文件的访问将会导致对已卸载的内 核模块代码内存区的访问。幸运的话我们最多获得一个讨厌的错误警告。如果此时已经在 该内存区加载了另一个模块,倒霉的你将会在内核中跳转执行意料外的代码。结果是无法 预料的,而且多半是不那么令人愉快的。

平常,当你不允许某项操作时,你会得到该操作返回的错误值(一般为一负的值)。 但对于无返回值的函数
cleanup_module
这是不可能的。然而,却有 一个计数器跟踪着有多少进程正在使用该模块。你可以通过查看文件
/proc/modules的第三列来获取这些信息。如果该值非零,则卸载 就会失败。你不需要在你模块中的函数
cleanup_module
中检查该 计数器,因为该项检查由头文件linux/module.c中定义的系统调用
sys_delete_module
完成。你也不应该直接对该计数器进行操作。 你应该使用在文件linux/modules.h定义的宏 来增加,减小和读取该计数器:

 

try_module_get(THIS_MODULE): Increment the use count.

try_module_put(THIS_MODULE): Decrement the use count.

保持该计数器时刻精确是非常重要的;如果你丢失了正确的计数,你将无法卸载模块, 那就只有重启了。不过这种情况在今后编写内核模块时也是无法避免的。

chardev.c

下面的代码示范了一个叫做chardev的字符设备。你可以用
cat输出该设备文件的内容(或用别的程序打开它)时,驱动模块 会将该设备文件被读取的次数显示。目前对设备文件的写操作还不被支持(像echo "hi" > /dev/hello),但会捕捉这些操作并且告诉用户该操作不被支持。不要担心我 们对读入缓冲区的数据做了什么;我们什么都没做。我们只是读入数据并输出我们已经接 收到的数据的信息。

Example 4-1. chardev.c

/*
* chardev.c: Creates a read-only char device that says how many times
* you've read from the dev file
*/

#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/fs.h>
#include <asm/uaccess.h> /* for put_user */

/*
* Prototypes - this would normally go in a .h file
*/
int init_module(void);
void cleanup_module(void);
static int device_open(struct inode *, struct file *);
static int device_release(struct inode *, struct file *);
static ssize_t device_read(struct file *, char *, size_t, loff_t *);
static ssize_t device_write(struct file *, const char *, size_t, loff_t *);

#define SUCCESS 0
#define DEVICE_NAME "chardev" /* Dev name as it appears in /proc/devices */
#define BUF_LEN 80 /* Max length of the message from the device */

/*
* Global variables are declared as static, so are global within the file.
*/

static int Major; /* Major number assigned to our device driver */
static int Device_Open = 0; /* Is device open?
* Used to prevent multiple access to device */
static char msg[BUF_LEN]; /* The msg the device will give when asked */
static char *msg_Ptr;

static struct file_operations fops = { .read = device_read, .write = device_write, .open = device_open, .release = device_release };
/*
* Functions
*/

int init_module(void)
{
Major = register_chrdev(0, DEVICE_NAME, &fops);

if (Major < 0) {
printk("Registering the character device failed with %d/n",
Major);
return Major;
}

printk("<1>I was assigned major number %d. To talk to/n", Major);
printk("<1>the driver, create a dev file with/n");
printk("'mknod /dev/hello c %d 0'./n", Major);
printk("<1>Try various minor numbers. Try to cat and echo to/n");
printk("the device file./n");
printk("<1>Remove the device file and module when done./n");

return 0;
}

void cleanup_module(void)
{
/*
* Unregister the device
*/
int ret = unregister_chrdev(Major, DEVICE_NAME);
if (ret < 0)
printk("Error in unregister_chrdev: %d/n", ret);
}

/*
* Methods
*/

/*
* Called when a process tries to open the device file, like
* "cat /dev/mycharfile"
*/
static int device_open(struct inode *inode, struct file *file)
{
static int counter = 0;
if (Device_Open)
return -EBUSY;
Device_Open++;
sprintf(msg, "I already told you %d times Hello world!/n", counter++);
msg_Ptr = msg;
try_module_get(THIS_MODULE);

return SUCCESS;
}

/*
* Called when a process closes the device file.
*/
static int device_release(struct inode *inode, struct file *file)
{
Device_Open--; /* We're now ready for our next caller */

/*
* Decrement the usage count, or else once you opened the file, you'll
* never get get rid of the module.
*/
module_put(THIS_MODULE);

return 0;
}

/*
* Called when a process, which already opened the dev file, attempts to
* read from it.
*/
static ssize_t device_read(struct file *filp, /* see include/linux/fs.h */
char *buffer, /* buffer to fill with data */
size_t length, /* length of the buffer */
loff_t * offset)
{
/*
* Number of bytes actually written to the buffer
*/
int bytes_read = 0;

/*
* If we're at the end of the message,
* return 0 signifying end of file
*/
if (*msg_Ptr == 0)
return 0;

/*
* Actually put the data into the buffer
*/
while (length && *msg_Ptr) {

/*
* The buffer is in the user data segment, not the kernel
* segment so "*" assignment won't work. We have to use
* put_user which copies data from the kernel data segment to
* the user data segment.
*/
put_user(*(msg_Ptr++), buffer++);

length--;
bytes_read++;
}

/*
* Most read functions return the number of bytes put into the buffer
*/
return bytes_read;
}

/*
* Called when a process writes to dev file: echo "hi" > /dev/hello
*/
static ssize_t
device_write(struct file *filp, const char *buff, size_t len, loff_t * off)
{
printk("<1>Sorry, this operation isn't supported./n");
return -EINVAL;
}


为多个版本的内核编写内核模块

系统调用,也就是内核提供给进程的接口,基本上是保持不变的。也许会添入新的 系统调用,但那些已有的不会被改动。这对于向下兼容是非常重要的。在多数情况下,设 备文件是保持不变的。但内核的内部在不同版本之间还是会有区别的。

Linux内核分为稳定版本(版本号中间为偶数)和试验版本(版本号中间为奇数)。 试验版本中可以试验各种各样的新而酷的主意,有些会被证实是一个错误,有些在下一版 中会被完善。总之,你不能依赖这些版本中的接口(这也是我不在本文档中支持它们的原因, 它们更新的太快了)。在稳定版本中,我们可以期望接口保持一致,除了那些修改代码中错误的版本。

如果你要支持多版本的内核,你需要编写为不同内核编译的代码树。可以通过比较宏
LINUX_VERSION_CODE和宏KERNEL_VERSION在版本号为a.b.c 的内核中,该宏的值应该为 2^16×a+2^8×b+c

在上一个版本中该文档还保留了详细的如何向后兼容老内核的介绍,现在我们决定打破这个传统。 对为老内核编写驱动感兴趣的读者应该参考对应版本的LKMPG,也就是说,2.4.x版本的LKMPG对应 2.4.x的内核,2.6.x版本的LKMPG对应2.6.x的内核。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: