一些关于Linux内核中常用的结构体函数指针的理解
动机
在看linux内核代码的时候经常能够看到一些结构体里面的成员跟我们以往见的到一些结构体不一样,常见的架构体如下面的代码:
struct a{int i;char b;struct c;
};
而内核中又见有这样的一些结构体:
const struct a{.a = read,.b = write,};
这种结构体我很少见,而且很多书籍上没有见过这样的结构体,搜索了一些资料,写下一些对这些结构体的理解
例子
首先放上三段代码作为一个例子
先放上第一段代码:
struct address_space_operations {int (*writepage)(struct page *page, struct writeback_control *wbc);int (*readpage)(struct file *, struct page *);/* Write back some dirty pages from this mapping. */int (*writepages)(struct address_space *, struct writeback_control *);/* Set a page dirty. Return true if this dirtied it */int (*set_page_dirty)(struct page *page);int (*readpages)(struct file *filp, struct address_space *mapping,struct list_head *pages, unsigned nr_pages);int (*write_begin)(struct file *, struct address_space *mapping,loff_t pos, unsigned len, unsigned flags,struct page **pagep, void **fsdata);int (*write_end)(struct file *, struct address_space *mapping,loff_t pos, unsigned len, unsigned copied,struct page *page, void *fsdata);/* Unfortunately this kludge is needed for FIBMAP. Don't use it */sector_t (*bmap)(struct address_space *, sector_t);void (*invalidatepage) (struct page *, unsigned int, unsigned int);int (*releasepage) (struct page *, gfp_t);
}
这是第二段代码:
const struct address_space_operations v9fs_addr_operations = {.readpage = v9fs_vfs_readpage,.readpages = v9fs_vfs_readpages,.set_page_dirty = __set_page_dirty_nobuffers,.writepage = v9fs_vfs_writepage,.write_begin = v9fs_write_begin,.write_end = v9fs_write_end,.releasepage = v9fs_release_page,.invalidatepage = v9fs_invalidate_page,.launder_page = v9fs_launder_page,.direct_IO = v9fs_direct_IO,
};
这是第三段代码:
static int v9fs_vfs_readpage(struct file *filp, struct page *page)
{return v9fs_fid_readpage(filp->private_data, page);
}
首先,我们可以看到,我们常见的那种结构体形式就是第二段代码中的这种形式。而弄懂这种形式就是我们这个文章的目的。
我们先需要知道的是,这个结构体里面的东西是函数指针,也就是说,这个成员的变量是跟函数有关的。
我们先看第二段代码,第二段代码除了里面的东西以外,外面的那层还是我们熟悉的东西:结构体
const struct address_space_operations v9fs_addr_operations
看到这个,我们或许知道了v9fs_addr_operations是类型为结构体address_space_operations的变量。
好了,现在我们知道v9fs_addr_operations的类型是结构体address_space_operations,那我们就看看第一段代码:
第一段代码里面,我们看到了很多声明,例如
int (*readpage)(struct file *, struct page *);
这里就是声明了结构体中address_space_operations的一个readpage的函数指针,说明了结构体address_space_operations中有一个名为readpage的,入参为(struct file *, struct page *)的一个函数。
至此,我们就知道了这个结构体里面的声明是咋回事了。
然后我们再看回第二段代码,这时我们就知道了v9fs_addr_operations中的readpage是指向函数v9fs_vfs_readpage的了。但是v9fs_vfs_readpage又是什么东西呢?这个时候请看第三段代码。
这里就实现了v9fs_vfs_readpage。v9fs_vfs_readpage里面实际调用的是v9fs_fid_readpage(filp->private_data, page);这个函数。
因此,大体的流程我归纳如下:
v9fs_addr_operations.readpage(filp->private_data, page) -> v9fs_fid_readpage(filp->private_data, page);
那为什么要搞的这么复杂呢?
我个人认为,这种方法有点类似于面向对象的抽象。把一大类具有相同特征的操作抽象出来,用我们文章中的例子,对于地址空间的操作,基本就是读、写、设脏等等的操作,这样可以把这些操作抽象成一个对象,在这里用一个结构体struct address_space_operations来搞定,这样,address_space_operations就是这些操作的一个父类。
然后现在要对v9fs进行操作,我们只需要实例化一个具体针对v9fs的对象,这时候我们就生成一个类型为struct address_space_operations的对象v9fs_addr_operations。在里面将需要调用的操作指向我们具体的函数,就能实现对于多种函数实现的情景下统一接口拉