10.2 Manually derive the relationship between file, cdev, and inode in Linux

It’s time to manually deduce the relationship between base classes, parent classes and subclasses in Linux
Put the code last

Simple explanation version

Detailed process

The first step is to register the driver

The cdev structure can be regarded as a base class, so the cdev of the character device driver in the linked list is serialized together. The serial port, LCD, is connected through cdev->list_head
The cdev structure contains the primary and secondary device numbers.
The first step is to use register_chrdev to create a new cdev base class in the kernel, and save the driver’s file_operation and major and minor device numbers in cdev.
At this time, put the cdev into the overall linked list: the linked list is serialized with the cdev of the character device driver, serial port, LCD
Among our drivers:
register_chrdev(CHRDEVBASE_MAJOR, CHRDEVBASE_NAME, & amp;chrdevbase_fops);
struct cdev *cdev;//Created cdev
cdev = cdev_alloc();
cdev->owner = fops->owner;
cdev->ops = fops;//cdev records f_op

kobject_set_name( & amp;cdev->kobj, "%s", name); //Assign a name to cdev's kobj, which is the name passed in
cdev_add(cdev, MKDEV(cd->major, baseminor), count);//cdev is added to the cdev linked list
cdev->dev = dev; //cdev saves the primary and secondary device numbers
cdev->count = count;//cdev saves the number of devices
kobj_map(cdev_map, dev, count, NULL, //Register this new dev into the kobj_map type table cdev_map. The map can be upgraded later.
exact_match, exact_lock, p); //cdev_map has our cdev
struct probe *p;
p = kmalloc_array(n, sizeof(struct probe), GFP_KERNEL); //Create a new probe to save cdev
p->owner = module;
p->dev = dev;
p->data = data;//cdev assignment successful
kobj_map type initially contains a bunch of probes, which can also be called a probe linked list.
A probe points to another 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;//The above cdev pointer is saved, so that in this map, cedv is connected together.
} *probes[255];
struct mutex *lock;
};
The second step is to create a file visible to user space, which is to initialize the inode node

inode object: describes a file in the file system, metadata (permissions, whether the type is a character device file, creation time)
It can also be understood as the information output by ls -l
crw–w—- 1 root tty 4, 1 October 3 23:09 tty1
struct inode {
umode_t i_mode;//Mode, such as crwx-rx from ls-l, can distinguish character devices and ordinary files
kuid_t i_uid;
kgid_t i_gid;
dev_t i_rdev;//If the file is a character device, there is a device number in the inode, //as 4 1 above
const struct file_operations *i_fop;
}

There are two methods in the picture here. 1. After the driver is initialized, use mknode to manually create a device node.
mknode needs to carry the primary and secondary device numbers in order to construct an inode node.

Creating a character device will finally call init_spectial_inode()

fs/inode.c
void init_special_inode(struct inode *inode, umode_t mode, dev_t rdev);//The inode node is passed in, but it is a character device, so the construction must continue
inode->i_mode = mode;
if (S_ISCHR(mode)) //Because it is the character device crw--w---- c
inode->i_fop = & amp;def_chr_fops;//Assign the default char_fop to the fop of this inode node
.open = chrdev_open,
inode->i_rdev = rdev;

The other is to use kobj_add when cdev_add is used to add inode nodes. I will find a place to talk about the kobj base class in detail later. I didn’t see kobj_add and other functions this time.
This step is to construct the file /dev/led

The third step is to open the file with the application, and the fourth step is to associate the file_operation of the driver

When a process opens a file, the vfs layer in the kernel space will generate a struct file object.
struct file{
struct path f_path; //The path of the file
const struct file_operations *f_op;//f_op!!!
struct inode *f_inode; // also secretly hides the inode
unsigned int f_flags;//flags
fmode_t f_mode;//Mode
loff_t f_pos;//Offset
void *private_data;//universal pointer
}

At the same time, put this file structure in fd_table. Each file structure corresponds to this fd_table index number. This index number will be returned after success.
At this time, the application space calls fd = open(“dev/led”, O_RDWR (flag), 0666 (mode)); the returned fd value is the corresponding fd value of this application in the vfs space
All files opened by the process are saved through fd_table

Application:
fd = open("dev/led", O_RDWR(flag),0666(mode));
Calling open() of glibc triggers a system call, generates a 0x80 interrupt, and enters the kernel layer.
vfs layer:open.c
SYSCALL_DEFINE3(open, const char __user *, filename, int, flags, umode_t, mode)
The above macro definition of the system call just means that a system call function called sys_open is created and three parameters are passed.
Isn’t this corresponding?
do_sys_open(AT_FDCWD, filename, flags, mode);
fd = get_unused_fd_flags(flags);//Find an unused fd to allocate fd to the file you just opened
struct file *f = do_filp_open(dfd, tmp, & amp;op);//Created a file structure pointer
do_file_open()
//pathname parses the file path layer by layer. You can refer to the file system analysis written last time.
//Finally get the dentry and vfsmount of the file to be opened, and save them in the structure struct path.
            //The structure struct path is stored in the struct nameidata nd variable, and the nd variable is also the input parameter of path_openat
set_nameidata( & amp;nd, dfd, pathname);
// Used to find file nodes based on nd and open the open function registered for the file
filp = path_openat( & amp;nd, op, flags | LOOKUP_RCU);
//Initialize file
file = alloc_empty_file(op->open_flag, current_cred());//Create a file
do_last(nd, file, op)) > 0
error = lookup_open(nd, & path, file, op, got_write);//Finally looking for your 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);//Get the fop saved by inode to the fop of file
open = f->f_op->open;
open(inode, f);//Open is executed, and it can also be seen here that the input parameters are inode nodes and file files.
\t\t\t\t\t\t\t
\t\t\t\t\t\t
.open = chrdev_open,//It’s a bit complicated here. Anyway, the fop->open of inode is called in the end, which is the open of char_dev.c, and the open assigned in the second step.
chrdev_open(struct inode *inode, struct file *filp)
kobj = kobj_lookup(cdev_map, inode->i_rdev, & idx);//From the cdev linked list, kobj is found, and the relationship is kobj_map->data(cdev)->kobj
cdev *new = container_of(kobj, struct cdev, kobj);//By the way, I found the cdev registered by the driver.
inode->i_cdev = p = new;//At the same time, the cdev in the inode node is also assigned, which is equivalent to the inode also knowing the cdev of the driver.
fops = fops_get(p->ops);//Get the fops of the driver
replace_fops(filp, fops);//Replace the fops of file
ret = filp->f_op->open(inode, filp);//Execute the fop->open function in the driver
//Enter our driver. The inode passed is the ionde of this module, and the file is the file opened by the process.
int chrdevbase_open(struct inode *inode, struct file *filp)
fd_install(fd, f);//Associate the file file with this fd, then I will record this file in fd_arry of our process, and also change fop_
The fifth step, execute other write and other functions
The application executed ioctrl(fd,cmd,arg);
Call glibc’s ioctrl
system call
0x80 interrupt
Enter the kernel:
VFS layer 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);//Find the file structure in the array according to the fd number passed by the application layer
do_vfs_ioctl(f.file, fd, cmd, arg);//Pass in the file structure and corresponding instructions
//Get the inode node from file
//file->dentry->inode
struct inode *inode = file_inode(filp);
if (S_ISREG(inode->i_mode)) //i_noded file type, if it is a normal file, S_isreg regular
error = file_ioctl(filp, cmd, arg);
else //That is the character file
error = vfs_ioctl(filp, cmd, arg);
error = filp->f_op->unlocked_ioctl(filp, cmd, arg);
//Because the operation of the file was changed to that in our driver when it was opened.
static struct file_operations chrdevbase_fops = {<!-- -->
unlocked_ioctl= ioctl,
}//Now let’s go inside our driver
long (*unlocked_ioctl) (struct file *, unsigned int, unsigned long);

Code block

driver

#include <linux/types.h>
#include <linux/kernel.h>
#include <linux/delay.h>
#include <linux/ide.h>
#include <linux/init.h>
#include <linux/module.h>
/****************************************************** ****************
Copyright ? ALIENTEK Co., Ltd. 1998-2029. All rights reserved.
File name: chrdevbase.c
Author: Zuo Zhongkai
Version: V1.0
Description: chrdevbase driver file.
Others: None
Forum: www.openedv.com
Log: First version V1.0 2019/1/30 Created by Zuo Zhongkai
*************************************************** *************/

#define CHRDEVBASE_MAJOR 200 /* Major device number */
#define CHRDEVBASE_NAME "chrdevbase" /* Device name */

static char readbuf[100]; /* read buffer */
static char writebuf[100]; /* write buffer */
static char kerneldata[] = {<!-- -->"kernel data!"};

/*
 * @description: open the device
 * @param - inode: the inode passed to the driver
 * @param - filp: device file, the file structure has a member variable called private_data
 * Generally, private_data points to the device structure when opening.
 * @return: 0 success; others failure
 */
static int chrdevbase_open(struct inode *inode, struct file *filp)
{<!-- -->
//printk("chrdevbase open!\r\\
");
return 0;
}

/*
 * @description: Read data from the device
 * @param - filp: the device file (file descriptor) to be opened
 * @param - buf: data buffer returned to user space
 * @param - cnt: length of data to be read
 * @param - offt: offset relative to the first address of the file
 * @return: The number of bytes read. If it is a negative value, it means the read failed.
 */
static ssize_t chrdevbase_read(struct file *filp, char __user *buf, size_t cnt, loff_t *offt)
{<!-- -->
int retvalue = 0;
\t
/* Send data to user space */
memcpy(readbuf, kerneldata, sizeof(kerneldata));
retvalue = copy_to_user(buf, readbuf, cnt);
if(retvalue == 0){<!-- -->
printk("kernel senddata ok!\r\\
");
}else{<!-- -->
printk("kernel senddata failed!\r\\
");
}
\t
//printk("chrdevbase read!\r\\
");
return 0;
}

/*
 * @description: Write data to the device
 * @param - filp: device file, representing an open file descriptor
 * @param - buf: the data to be written to the device
 * @param - cnt: length of data to be written
 * @param - offt: offset relative to the first address of the file
 * @return: The number of bytes written. If it is a negative value, it means the writing failed.
 */
static ssize_t chrdevbase_write(struct file *filp, const char __user *buf, size_t cnt, loff_t *offt)
{<!-- -->
int retvalue = 0;
/* Receive data passed from user space to the kernel and print it out */
retvalue = copy_from_user(writebuf, buf, cnt);
if(retvalue == 0){<!-- -->
printk("kernel recevdata:%s\r\\
", writebuf);
}else{<!-- -->
printk("kernel recevdata failed!\r\\
");
}
\t
//printk("chrdevbase write!\r\\
");
return 0;
}

/*
 * @description: close/release the device
 * @param - filp: the device file (file descriptor) to be closed
 * @return: 0 success; others failure
 */
static int chrdevbase_release(struct inode *inode, struct file *filp)
{<!-- -->
//printk("chrdevbase release!\r\\
");
return 0;
}

/*
 * Device operation function structure
 */
static struct file_operations chrdevbase_fops = {<!-- -->
.owner = THIS_MODULE,
.open = chrdevbase_open,
.read = chrdevbase_read,
.write = chrdevbase_write,
.release = chrdevbase_release,
};

/*
 * @description: driver entry function
 * @param: None
 * @return: 0 success; others failure
 */
static int __init chrdevbase_init(void)
{<!-- -->
int retvalue = 0;

/* Register character device driver */
retvalue = register_chrdev(CHRDEVBASE_MAJOR, CHRDEVBASE_NAME, & amp;chrdevbase_fops);
if(retvalue < 0){<!-- -->
printk("chrdevbase driver register failed\r\\
");
}
printk("chrdevbase init!\r\\
");
return 0;
}

/*
 * @description: driver export function
 * @param: None
 * @return: None
 */
static void __exit chrdevbase_exit(void)
{<!-- -->
/* Unregister character device driver */
unregister_chrdev(CHRDEVBASE_MAJOR, CHRDEVBASE_NAME);
printk("chrdevbase exit!\r\\
");
}

/*
 * Specify the above two functions as the driver's entry and exit functions
 */
module_init(chrdevbase_init);
module_exit(chrdevbase_exit);

/*
 * LICENSE and author information
 */
MODULE_LICENSE("GPL");
MODULE_AUTHOR("zuozhongkai");