WARN_ONCE感觉真是神奇,可以打印函数的调用关系,很是牛逼,fbmem.c是显示驱动的统合管理模块,所以想了解下它的open,read,write函数的调用关系。
首先看一下加入的地方(注意WARN_ONCE 指定 打印出用 EXPORT_SYMBOL定义的函数)。
diff --git a/drivers/video/fbdev/core/fbmem.c b/drivers/video/fbdev/core/fbmem.c
index 76c1ad9..3e55ffc 100644
--- a/drivers/video/fbdev/core/fbmem.c
+++ b/drivers/video/fbdev/core/fbmem.c
@@ -752,7 +752,7 @@ fb_read(struct file *file, char __user *buf, size_t count, loff_
u8 __iomem *src;
int c, cnt = 0, err = 0;
unsigned long total_size;
-
+ WARN_ONCE(1, "hellomxg\n");
if (!info || ! info->screen_base)
return -ENODEV;
@@ -817,7 +817,7 @@ fb_write(struct file *file, const char __user *buf, size_t count
u8 __iomem *dst;
int c, cnt = 0, err = 0;
unsigned long total_size;
-
+ WARN_ONCE(1, "hellomxg\n");
if (!info || !info->screen_base)
return -ENODEV;
@@ -1397,6 +1397,8 @@ fb_mmap(struct file *file, struct vm_area_struct * vma)
unsigned long start;
u32 len;
+ WARN_ONCE(1, "hellomxg\n");
+
if (!info)
return -ENODEV;
fb = info->fbops;
@@ -1443,7 +1445,7 @@ __releases(&info->lock)
int fbidx = iminor(inode);
struct fb_info *info;
int res = 0;
-
+ WARN_ONCE(1, "hellomxg\n");
info = get_fb_info(fbidx);
之后随便运行个显示应用程序,或者用【cat /dev/urandom > /dev/fb0】命令,之后 dmesg中可以看到。
■fb_open函数
[ 169.076711] [<c03f5020>] (fb_open) from [<c0207c58>] (chrdev_open+0x14c/0x17c)
[ 169.084809] [<c0207c58>] (chrdev_open) from [<c0200edc>] (do_dentry_open+0x29c/0x310)
[ 169.093599] [<c0200edc>] (do_dentry_open) from [<c0212654>] (path_openat+0xe44/0x101c)
[ 169.102483] [<c0212654>] (path_openat) from [<c0212868>] (do_filp_open+0x3c/0x9c)
[ 169.110874] [<c0212868>] (do_filp_open) from [<c02022f8>] (do_sys_open+0x104/0x230)
[ 169.119468] [<c02022f8>] (do_sys_open) from [<c0107360>] (ret_fast_syscall+0x0/0x48)
这个函数说明前,要说一下这个 devtmpfs 文件系统了,系统初始化的时候,如果配置了devtmpfs的话,会生成一个线程 devtmpfsd,
当我们调用 device_create时,其中的 devcie_add函数内部会调用devtmpfs_create_node函数,此函数会通知 devtmpfsd线程让其调用mknod函数建立设备节点,即,函数:error = dir->i_op->mknod(dir, dentry, mode, dev);
而这个 mknod要根据devtmpfs 具体挂载到了哪个文件系统而定,比如说挂载到了ext4上了,那就调用 ext4上的 mknode函数,mknode会调用init_special_inode用来初始化此设备inode的属性
void init_special_inode(struct inode *inode, umode_t mode, dev_t rdev)
{
inode->i_mode = mode;
if (S_ISCHR(mode)) {
inode->i_fop = &def_chr_fops;
inode->i_rdev = rdev;
即,定义了 /dev/xx 这个xxinode节点的 i_fop,可以进一步看到 i_fop->open就是 chrdev_open函数。
另外 将设备号也保存了下来(inode->i_rdev = rdev)
好的,接下来看调用栈,通过 path_openat找到 /dev/xx 这个inode节点后,会生成一个file = get_empty_filp(); 并调用do_dentry_open,
do_dentry_open这个函数做了几个牛逼的事情。
struct file *f,
①f->f_inode = inode;
②f->f_op = fops_get(inode->i_fop); // f->f_open=inode->i_fop,即f->f_open=chrdev_open
③open = f->f_op->open; //即,调用函数chrdev_open
此函数呢,就会在 cdev_map 通过 mknode注册的inode->i_rdev来找 register_chrdev注册的cdev
并无耻的将 f->f_op = cdev->ops//确定了关系
struct file 里面的f_op 变成了 cdev实现的 file_operations 结构体。
■fb_write函数
[ 265.724692] [<c03f44d0>] (fb_write) from [<c0202ad0>] (__vfs_write+0x28/0x12c)
[ 265.732794] [<c0202ad0>] (__vfs_write) from [<c0203848>] (vfs_write+0xb8/0x190)
[ 265.740983] [<c0203848>] (vfs_write) from [<c020479c>] (SyS_write+0x4c/0x9c)
[ 265.748886] [<c020479c>] (SyS_write) from [<c0107360>] (ret_fast_syscall+0x0/0x48)
接下来,应用程序就通过 open生成的 struct file 来搞事情。
ssize_t __vfs_write(struct file *file, const char __user *p, size_t count,
loff_t *pos)
{
if (file->f_op->write)
return file->f_op->write(file, p, count, pos); //调用fb_write
else if (file->f_op->write_iter)
return new_sync_write(file, p, count, pos);
else
return -EINVAL;
}
■fb_mmap函数
[ 169.233673] [<c03f3cb0>] (fb_mmap) from [<c01e0124>] (mmap_region+0x364/0x538)
[ 169.241779] [<c01e0124>] (mmap_region) from [<c01e062c>] (do_mmap+0x334/0x3c0)
[ 169.249872] [<c01e062c>] (do_mmap) from [<c01cbcf0>] (vm_mmap_pgoff+0x94/0xdc)
[ 169.257982] [<c01cbcf0>] (vm_mmap_pgoff) from [<c01de788>] (SyS_mmap_pgoff+0xac/0xc4)
[ 169.266768] [<c01de788>] (SyS_mmap_pgoff) from [<c0107360>] (ret_fast_syscall+0x0/0x48)
mmap_region函数内部:
error = file->f_op->mmap(file, vma);
这里顺便提一嘴 fb_mmap这个函数,此函数会调用
int vm_iomap_memory(struct vm_area_struct *vma, phys_addr_t start, unsigned long len)
{
unsigned long pfn = start >> PAGE_SHIFT;
unsigned long vm_len = vma->vm_end - vma->vm_start;
pfn += vma->vm_pgoff;
return io_remap_pfn_range(vma, vma->vm_start, pfn, vm_len, vma->vm_page_prot);
}
来对 start这个物理地址进行映射,映射到用户空间的vma里。
但是这里有个 pfn += vma->vm_pgoff,即为什么要加上vma->vm_pgoff吗?直接用 fpn不香吗?
这里我们要结合应用程序中的mmap函数进行理解,考虑下面一个场景,当应用程序用mmap映射一个设备时,mmap的最后一个参数一般我们设置为0,没去管它,它代表的是 映射的偏移量(必须是page的整数倍),比如说
假设/dev/fb0 提供的物理地址范围是: A~B
在用户层执行mmap:
map_base = mmap(NULL, 0xff, PROT_READ|PROT_WRITE, MAP_SHARED, fd, 1*PAGESZIE);
对应的结果是将物理地址 (A+1 * PAGESZIE)~ B 的范围 映射到了 用户空间,
即,用户空间map_base虚拟地址对应的物理地址 不是A,而是(A + 1 * PAGESZIE)即:A地址的基础上偏移了 1 * PAGESZIE,
另外:在mmap系统调用的过程中,已将vma->vm_pgoff赋值为:1 * PAGESZIE / PAGESZIE = 1;
所以不难看出,vma->vm_pgoff作为匿名页时(不是文件数据)时的作用是 :要从映射设备的物理起始地址A开始,偏移vma->vm_pgoff * PAGESZIE ,即从(A+vma->vm_pgoff * PAGESZIE)这个地址开始映射,所以vma->vm_pgoff 可以定义为:要映射的设备地址的偏移(偏移是以页为单位)。
一般我们没有偏移映射的需求,所以用户层中mmap的最后一个参数一般都是0,即vma->vm_pgoff为0。
这么这个物理地址A是指的是什么呢?
比如说LCD驱动中,驱动 probe后,就会赋值info->fix.mmio_start(显存物理地址,dma_malloc申请的连续内存,并将mmio_start内存地址赋值给lcd控制器的frambufer寄存器中)。
这个显存地址mmio_start呢,就是上边提到的地址A,需要通过用户层的mmap来映射
....
start = info->fix.smem_start;
....
return vm_iomap_memory(vma, start, len);
■fb_mmap函数再谈
fb_mmap刚开始为了获得 fb_info有一段
static struct fb_info *file_fb_info(struct file *file)
{
struct inode *inode = file->f_path.dentry->d_inode;
int fbidx = iminor(inode);
struct fb_info *info = registered_fb[fbidx];
if (info != file->private_data)
info = NULL;
return info;
}
fb_mmap(
struct file *file, struct vm_area_struct * vma)
{
struct fb_info *info = file_fb_info(file);
....
}
即获取 inode 通过 file->f_path.dentry->d_inode; 就可以获取,那个过程又是如何来的呢?
我们知道,实际上在linux下一切都是文件,/dev/xx 等设备文件当然也不例外,在 创建 设备节点的时候,当然也要申请一个 设备相关的inode,并且融入与 vfs的调用过程中,我们可以看一下,当调用节点的时候,调用mknode函数,我们参考一个简单的 mknod
static void __d_instantiate(struct dentry *dentry, struct inode *inode)
{
spin_lock(&dentry->d_lock);
if (inode) {
if (unlikely(IS_AUTOMOUNT(inode)))
dentry->d_flags |= DCACHE_NEED_AUTOMOUNT;
list_add(&dentry->d_u.d_alias, &inode->i_dentry);
}
dentry->d_inode = inode;
dentry_rcuwalk_barrier(dentry);
spin_unlock(&dentry->d_lock);
fsnotify_d_instantiate(dentry, inode);
}
void d_inst
antiate(struct dentry *entry, struct inode * inode)
{
BUG_ON(!list_empty(&entry->d_u.d_alias));
if (inode)
spin_lock(&inode->i_lock);
__d_instantiate(entry, inode);
if (inode)
spin_unlock(&inode->i_lock);
security_d_instantiate(entry, inode);
}
static i
nt usbfs_mknod (struct inode *dir, struct dentry *dentry, umode_t mode,
dev_t dev)
{
struct inode *inode = usbfs_get_inode(dir->i_sb, mode, dev);
int error = -EPERM;
if (dentry->d_inode)
return -EEXIST;
if (inode) {
d_instantiate(dentry, inode);
dget(dentry);
error = 0;
}
return error;
}
可以看到 __d_instantiate函数中建立了 【dentry->d_inode = inode】
之后当打开设备的时候,即回头在看一下 do_dentry_open 这个函数,其中里面的一段代码。
inode = dentry->d_inode;
if (f->f_mode & FMODE_WRITE) {
error = __get_file_write_access(inode, mnt);
if (error)
goto cleanup_file;
if (!special_file(inode->i_mode))
file_take_write(f);
}
f->f_mapping = inode->i_mapping;
f->f_path.dentry = dentry;
f->f_path.mnt = mnt;
f->f_pos = 0;
即 open的时候就已经 f->f_path.dentry = dentry; 了。
另外,附上根文件系统的挂载过程:
https://www.cnblogs.com/schips/p/13178870.html
简单讲,open 一个/dev/x 文件,这个 / 从哪里开始呢?
首先这个 / 是初始化文件系统时,挂载后生成的,之后将 超级快以及根的 dentry
通过 set_fs_root(current->fs, &root); 设定到 current中,之后 open函数就从 current->fs->root开始搞起
直接上这个"/“是内存建立的节点,后期通过mount挂接到 这个内存根节点”/" 下。
大致流程如下:
void __init mnt_init(void)
{
init_rootfs();
init_mount_tree();
}
init_rootfs函数,注册名为 rootfs的文件系统。
static struct file_system_type rootfs_fs_type = {
.name = "rootfs",
.mount = rootfs_mount,
.kill_sb = kill_litter_super,
};
int
__init init_rootfs(void)
{
int err;
err = bdi_init(&ramfs_backing_dev_info);
if (err)
return err;
err = register_filesystem(&rootfs_fs_type);
if (err)
bdi_destroy(&ramfs_backing_dev_info);
return err;
}
init_mount_tree函数
static void __init init_mount_tree(void)
{
struct vfsmount *mnt;
struct mnt_namespace *ns;
struct path root;
mnt = do_kern_mount("rootfs", 0, "rootfs", NULL);
if (IS_ERR(mnt))
panic("Can't create rootfs");
ns = create_mnt_ns(mnt);
if (IS_ERR(ns))
panic("Can't allocate initial namespace");
init_task.nsproxy->mnt_ns = ns;
get_mnt_ns(ns);
root.mnt = mnt;
root.dentry = mnt->mnt_root;
set_fs_pwd(current->fs, &root);
set_fs_root(current->fs, &root);
}
其中的 do_kern_mount 要挂载名为rootfs的文件系统(即上面 init_rootfs做的事儿)
此函数调用了 init_rootfs中注册的 rootfs_fs_type 的 mount函数
do_kern_mount
->vfs_kern_mount
->struct mount *mnt;
->struct dentry *root;
->mnt = alloc_vfsmnt(name);
->root = mount_fs(type, flags, name, data);//这里调用了rootfs_mount
->mnt->mnt.mnt_root = root;
->mnt->mnt.mnt_sb = root->d_sb;
->mnt->mnt_mountpoint = mnt->mnt.mnt_root;
->mnt->mnt_parent = mnt;
->root = type->mount(type, flags, name, data);
->sb = root->d_sb;