• 10.2手动推导linux中file, cdev, inode之间的关系


    是时候可以手动推导一下linux里面基类父类和子类的关系了
    代码放最后把
    在这里插入图片描述

    简单说明版

    详细流程

    第一步注册驱动

    cdev结构体能看做是一个基类,那么链表里面都是字符设备驱动的cdev连载一起,啥串口,lcd的,通过cdev->list_head连接
    那cdev结构体里有主次设备号
    第一步 使用register_chrdev 在内核创建了新的cdev基类,同时把驱动的file_operation和主次设备号保存在cdev中
    此时把cdev放入整体的链表中 : 链表里面都是字符设备驱动的cdev连载一起,啥串口,lcd
    我们的驱动中:
    register_chrdev(CHRDEVBASE_MAJOR, CHRDEVBASE_NAME, &chrdevbase_fops);
    struct cdev *cdev;//创造了cdev
    cdev = cdev_alloc();
    cdev->owner = fops->owner;
    cdev->ops = fops;//cdev记录下f_op

    kobject_set_name(&cdev->kobj, "%s", name); //给cdev的kobj赋值名字,也就是传入的名字
    cdev_add(cdev, MKDEV(cd->major, baseminor), count);//cdev加入cdev链表
    	cdev->dev = dev; //cdev保存主次设备号
    	cdev->count = count;//cdev保存个设备的个数
    	kobj_map(cdev_map, dev, count, NULL, //把这个新的dev注册进 kobj_map类型的表cdev_map中,map后续还能再升入一下
    		 exact_match, exact_lock, p); //cdev_map中就有了我们的cdev
    		 struct probe *p;
    		 p = kmalloc_array(n, sizeof(struct probe), GFP_KERNEL); //创建新的probe来保存cdev
    		 p->owner = module;
    		p->dev = dev;
    		p->data = data;//cdev赋值成功
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    kobj_map类型 起始就是包含了一堆probe 也能叫一个probe链表
    一个porbe指向另一个probe,
    struct kobj_map {
    	struct probe {
    		struct probe *next;
    		dev_t dev;
    		unsigned long range;
    		struct module *owner;
    		kobj_probe_t *get;
    		int (*lock)(dev_t, void *);
    		void *data;//就保存了上面的cdev指针,这样在这个map中,cedv就连在一起了
    	} *probes[255];
    	struct mutex *lock;
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    第二步创建用户空间能看见的文件,也就是初始化inode节点

    inode 对象:描述文件系统中的某个文件,元数据(权限,类型是否为字符设备文件这种.创建时间)
    也能理解为ls -l出来的信息
    crw–w---- 1 root tty 4, 1 10月 3 23:09 tty1
    struct inode {
    umode_t i_mode;//模式,如ls-l 出来的 crwx-rx,能区分字符设备和普通文件
    kuid_t i_uid;
    kgid_t i_gid;
    dev_t i_rdev;//如果文件时字符设备,inode里面就有设备号,//如上面的 4 1
    const struct file_operations *i_fop;
    }

    这里图上有两种办法1 驱动初始化后,使用mknode 手动创建一个设备节点
    mknode 需要带主次设备号 是为了构造一个inode节点
    在这里插入图片描述
    创建字符设备最后会调用到init_spectial_inode()

    fs/inode.c
    void init_special_inode(struct inode *inode, umode_t mode, dev_t rdev);//传入了inode节点,但是为字符设备,要继续构造
    	inode->i_mode = mode;
    	if (S_ISCHR(mode)) //因为是字符设备 crw--w---- 的c
    		inode->i_fop = &def_chr_fops;//对这个inode节点的fop赋值默认的char_fop
    			.open = chrdev_open,
    		inode->i_rdev = rdev;
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    另一个是cdev_add的时候使用kobj_add这些进行增加inode节点,后面找个地方细聊kobj基类,这次没看见kobj_add等函数
    这一步就是为了构造出 /dev/led这个文件

    第三步应用程序打开文件,第四部关联驱动的file_operation

    进程打开文件 内核空间的vfs层就会生成一个struct file 对象
    struct file{
    struct path f_path; //文件的路径
    const struct file_operations *f_op;//f_op!!!
    struct inode *f_inode; //还偷偷藏了inode
    unsigned int f_flags;//标志
    fmode_t f_mode;//模式
    loff_t f_pos;//偏移
    void *private_data;//万能指针
    }
    在这里插入图片描述

    同时把这个file结构体放在 fd_table中,每个file结构体对应了这个fd_table索引号,成功后返回这个索引号
    此时应用空间调用fd = open(“dev/led”, O_RDWR(标志),0666(模式)); 返回来的fd数值,也就是这个应用程序在vfs空间中对应的fd值
    通过 fd_table保存了该进程打开的所有文件

    应用程序:
    fd  = open("dev/led", O_RDWR(标志),0666(模式));
    调用到glibc的open() 触发系统调用 产生0x80中断 进入内核层
    vfs层:open.c
    SYSCALL_DEFINE3(open, const char __user *, filename, int, flags, umode_t, mode)
    上面这个系统调用的宏定义刚好就是说,建了一个系统调用的函数叫sys_open同时还传来三个参
    这不就对应上了嘛
    do_sys_open(AT_FDCWD, filename, flags, mode);
    	fd = get_unused_fd_flags(flags);//找一个没有用过的fd给刚打开的文件进行分配fd
    	struct file *f = do_filp_open(dfd, tmp, &op);//创建了一个file结构体指针
    		do_file_open()
    			//pathname一层一层解析文件路径,可参考上次写的文件系统分析
    			// 最后得到要打开文件的dentry和vfsmount,保存到结构体struct path中,
                // 而结构体struct path保存在struct nameidata nd变量中,nd变量也是path_openat的入参
    			set_nameidata(&nd, dfd, pathname);
    			// 用于根据nd寻找文件节点,并打开该文件注册的open函数
    			filp = path_openat(&nd, op, flags | LOOKUP_RCU);
    				 //初始化file
    				 file = alloc_empty_file(op->open_flag, current_cred());//创建一个file
    				 do_last(nd, file, op)) > 0
    				 	error = lookup_open(nd, &path, file, op, got_write);//终于寻找你的open了
    					dentry_open(const struct path *path, int flags,const struct cred *cred)
    						vfs_open(path, f);
    							do_dentry_open(struct file *f, struct inode *inode,int (*open)(struct inode *, struct file *))
    								f->f_op = fops_get(inode->i_fop);//获取inode保存的fop给file的fop
    								open = f->f_op->open;
    								open(inode, f);//执行open了,这里也看得出来入参为啥是inode节点和file文件了
    							
    						
    		.open = chrdev_open,//这里还有点复杂,反正最后调用了inoded的fop->open,也就是char_dev.c 的open,也是第二步赋值过来的open
    		chrdev_open(struct inode *inode, struct file *filp)
    			kobj = kobj_lookup(cdev_map, inode->i_rdev, &idx);//从cdev链表中,找到了kobj,关系为 kobj_map->data(cdev)->kobj
    			cdev *new =  container_of(kobj, struct cdev, kobj);//顺带就把驱动注册的cdev找出来了
    			inode->i_cdev = p = new;//同时还把inode节点里cdev一起赋值,相当于inode也知道驱动的cdev
    			fops = fops_get(p->ops);//拿到了驱动的fops
    			replace_fops(filp, fops);//替换file的fops
    			ret = filp->f_op->open(inode, filp);//执行驱动中的fop->open函数
    			//进入我们的驱动里.传来的inode是这个模块的ionde,file是进程打开的这个file
    			int chrdevbase_open(struct inode *inode, struct file *filp)
    	fd_install(fd, f);//关联file文件和这个fd,那我我们进程的数fd_arry中就把这个文件记录了,也把fop_改了
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    第五步,执行其他的write等函数
    应用执行了ioctrl(fd,cmd,arg);
    调用glibc的ioctrl
    系统调用
    0x80中断
    进入内核:
    VFS层 fs/ioctrl.c
    SYSCALL_DEFINE3(ioctl, unsigned int, fd, unsigned int, cmd, unsigned long, arg);
    ksys_ioctl(fd, cmd, arg);
    	struct fd f = fdget(fd);//根据应用层传递的fd编号,在数组中找到file结构体
    	do_vfs_ioctl(f.file, fd, cmd, arg);//传入file结构体,和对应的指令
    		//从file中取出inode节点
    		//file->dentry->inode
    		struct inode *inode = file_inode(filp);
    		if (S_ISREG(inode->i_mode)) //i_noded文件类型,如果是普通文件,S_isreg   regular常规的
    			error = file_ioctl(filp, cmd, arg);
    		else  //那就是字符文件了
    			error = vfs_ioctl(filp, cmd, arg);
    				error = filp->f_op->unlocked_ioctl(filp, cmd, arg);
    //因为file的operation在open的时候被改成我们的驱动里的了
    static struct file_operations chrdevbase_fops = {
    	unlocked_ioctl= ioctl,
    }//现在就到我们驱动内部
    long (*unlocked_ioctl) (struct file *, unsigned int, unsigned long);
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24

    代码块

    driver

    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    /***************************************************************
    Copyright © ALIENTEK Co., Ltd. 1998-2029. All rights reserved.
    文件名		: chrdevbase.c
    作者	  	: 左忠凯
    版本	   	: V1.0
    描述	   	: chrdevbase驱动文件。
    其他	   	: 无
    论坛 	   	: www.openedv.com
    日志	   	: 初版V1.0 2019/1/30 左忠凯创建
    ***************************************************************/
    
    #define CHRDEVBASE_MAJOR	200				/* 主设备号 */
    #define CHRDEVBASE_NAME		"chrdevbase" 	/* 设备名     */
    
    static char readbuf[100];		/* 读缓冲区 */
    static char writebuf[100];		/* 写缓冲区 */
    static char kerneldata[] = {"kernel data!"};
    
    /*
     * @description		: 打开设备
     * @param - inode 	: 传递给驱动的inode
     * @param - filp 	: 设备文件,file结构体有个叫做private_data的成员变量
     * 					  一般在open的时候将private_data指向设备结构体。
     * @return 			: 0 成功;其他 失败
     */
    static int chrdevbase_open(struct inode *inode, struct file *filp)
    {
    	//printk("chrdevbase open!\r\n");
    	return 0;
    }
    
    /*
     * @description		: 从设备读取数据 
     * @param - filp 	: 要打开的设备文件(文件描述符)
     * @param - buf 	: 返回给用户空间的数据缓冲区
     * @param - cnt 	: 要读取的数据长度
     * @param - offt 	: 相对于文件首地址的偏移
     * @return 			: 读取的字节数,如果为负值,表示读取失败
     */
    static ssize_t chrdevbase_read(struct file *filp, char __user *buf, size_t cnt, loff_t *offt)
    {
    	int retvalue = 0;
    	
    	/* 向用户空间发送数据 */
    	memcpy(readbuf, kerneldata, sizeof(kerneldata));
    	retvalue = copy_to_user(buf, readbuf, cnt);
    	if(retvalue == 0){
    		printk("kernel senddata ok!\r\n");
    	}else{
    		printk("kernel senddata failed!\r\n");
    	}
    	
    	//printk("chrdevbase read!\r\n");
    	return 0;
    }
    
    /*
     * @description		: 向设备写数据 
     * @param - filp 	: 设备文件,表示打开的文件描述符
     * @param - buf 	: 要写给设备写入的数据
     * @param - cnt 	: 要写入的数据长度
     * @param - offt 	: 相对于文件首地址的偏移
     * @return 			: 写入的字节数,如果为负值,表示写入失败
     */
    static ssize_t chrdevbase_write(struct file *filp, const char __user *buf, size_t cnt, loff_t *offt)
    {
    	int retvalue = 0;
    	/* 接收用户空间传递给内核的数据并且打印出来 */
    	retvalue = copy_from_user(writebuf, buf, cnt);
    	if(retvalue == 0){
    		printk("kernel recevdata:%s\r\n", writebuf);
    	}else{
    		printk("kernel recevdata failed!\r\n");
    	}
    	
    	//printk("chrdevbase write!\r\n");
    	return 0;
    }
    
    /*
     * @description		: 关闭/释放设备
     * @param - filp 	: 要关闭的设备文件(文件描述符)
     * @return 			: 0 成功;其他 失败
     */
    static int chrdevbase_release(struct inode *inode, struct file *filp)
    {
    	//printk("chrdevbase release!\r\n");
    	return 0;
    }
    
    /*
     * 设备操作函数结构体
     */
    static struct file_operations chrdevbase_fops = {
    	.owner = THIS_MODULE,	
    	.open = chrdevbase_open,
    	.read = chrdevbase_read,
    	.write = chrdevbase_write,
    	.release = chrdevbase_release,
    };
    
    /*
     * @description	: 驱动入口函数 
     * @param 		: 无
     * @return 		: 0 成功;其他 失败
     */
    static int __init chrdevbase_init(void)
    {
    	int retvalue = 0;
    
    	/* 注册字符设备驱动 */
    	retvalue = register_chrdev(CHRDEVBASE_MAJOR, CHRDEVBASE_NAME, &chrdevbase_fops);
    	if(retvalue < 0){
    		printk("chrdevbase driver register failed\r\n");
    	}
    	printk("chrdevbase init!\r\n");
    	return 0;
    }
    
    /*
     * @description	: 驱动出口函数
     * @param 		: 无
     * @return 		: 无
     */
    static void __exit chrdevbase_exit(void)
    {
    	/* 注销字符设备驱动 */
    	unregister_chrdev(CHRDEVBASE_MAJOR, CHRDEVBASE_NAME);
    	printk("chrdevbase exit!\r\n");
    }
    
    /* 
     * 将上面两个函数指定为驱动的入口和出口函数 
     */
    module_init(chrdevbase_init);
    module_exit(chrdevbase_exit);
    
    /* 
     * LICENSE和作者信息
     */
    MODULE_LICENSE("GPL");
    MODULE_AUTHOR("zuozhongkai");
    
    
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109
    • 110
    • 111
    • 112
    • 113
    • 114
    • 115
    • 116
    • 117
    • 118
    • 119
    • 120
    • 121
    • 122
    • 123
    • 124
    • 125
    • 126
    • 127
    • 128
    • 129
    • 130
    • 131
    • 132
    • 133
    • 134
    • 135
    • 136
    • 137
    • 138
    • 139
    • 140
    • 141
    • 142
    • 143
    • 144
    • 145
    • 146
    • 147
    • 148
    • 149
    • 150
    • 151
  • 相关阅读:
    CloudCompare源码分析之ccViewer模块(阅读经典)
    CameraServiceProxy启动-Android12
    力扣细节题:判断是否为平衡二叉树
    【ARM 安全系列介绍 3.7 -- SM4 对称加密算】
    通关剑指 Offer——剑指 Offer II 010. 和为 k 的子数组
    C#_键盘钩子
    GM8775C MIPI转LVDS调试资料分享
    常用的语音芯片工作原理_分类为语音播报 语音识别 语音合成tts
    git学习笔记
    Spring之BeanFactory与ApplicationContext区别、实例化Bean的三种⽅式、延迟加载(lazy-Init )
  • 原文地址:https://blog.csdn.net/weixin_43898067/article/details/133842848