The file_operations structure of the interactive mode of linux application and driver

Why does linux user mode need to drive and control hardware

1. Linux needs to provide users with unified operation functions, such as read(), write() and other API functions, so that people who write user mode code do not need to have a deep understanding of the hardware.
2. Linux needs to ensure the stability and security of the system. If the user can manipulate the registers at will, it may cause the system to crash. Professionals should leave it to professional people.

How to control hardware in linux user mode

It can be simply understood that the user uses open(), close(), read(), write() and other API functions to control the hardware, and the driver provides the implementation of these functions.
If there is a requirement: the user wants to control the on and off of a light and check the status of the light.
1. Linux says: everything about me is a file, if you want to do anything with me, just operate the file.
2. The driver of the light said: You only need to open the file I provided; write “1” and I will light up, write “0” and I will turn off; you can also read me, read out “1\ “I’m in the on state, the readout is “0”I’m in the off state; remember to close me after using the file.
3. User’s function realization: open->write->read->close realizes the function.

Standard interface provided by linux

As mentioned above, if a Linux application wants to control the hardware, it must control the hardware through the driver; so what methods are provided? Commonly used methods are these open(), close(), read(), write()…, of course, each method corresponds to a function in the driver, then the collection of these functions is the protagonist of today’s talk – the file_operations structure Packaged together, as shown in the figure:

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 (*write) (struct file *, const char __user *, size_t, loff_t *);
        ssize_t (*read_iter) (struct kiocb *, struct iov_iter *);
        ssize_t (*write_iter) (struct kiocb *, struct iov_iter *);
        int (*iterate) (struct file *, struct dir_context *);
        unsigned int (*poll) (struct file *, struct poll_table_struct *);
        long (*unlocked_ioctl) (struct file *, unsigned int, unsigned long);
        long (*compat_ioctl) (struct file *, unsigned int, unsigned long);
        int (*mmap) (struct file *, struct vm_area_struct *);
        int (*mremap)(struct file *, struct vm_area_struct *);
        int (*open) (struct inode *, struct file *);
        int (*flush) (struct file *, fl_owner_t id);
        int (*release) (struct inode *, struct file *);
        int (*fsync) (struct file *, loff_t, loff_t, 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 (*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);
        int (*check_flags)(int);
        int (*flock) (struct file *, int, struct file_lock *);
        ssize_t (*splice_write)(struct pipe_inode_info *, struct file *, loff_t *, size_t, unsigned int);
        ssize_t (*splice_read)(struct file *, loff_t *, struct pipe_inode_info *, size_t, unsigned int);
        int (*setlease)(struct file *, long, struct file_lock **, void **);
        long (*fallocate)(struct file *file, int mode, loff_t offset,
                          loff_t len);
        void (*show_fdinfo)(struct seq_file *m, struct file *f);
#ifndef CONFIG_MMU
        unsigned (*mmap_capabilities)(struct file *);
#endif
};

How to write a driver under linux

It can be seen from the above that the Linux driver actually implements the method in file_operations, and the application can call this method through the standard interface to control the hardware; I don’t know if you have found a problem, even if I implement the method in file_operations, I How should I tell Linux to implement these methods? Also, under linux, it is definitely not just my driver that implements these methods, so how does linux find this method that I implemented?
1. Tell the kernel through the register_chrdev() function that I want to register these methods into the kernel. The prototype of this function is as follows:

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

It can be known that the third parameter is the file_operations structure we are in love with, just pass the address of the file_operations structure we implemented to this function, is it very simple?
2. The above also mentioned how linux finds the method we need in many file_operations. The application driver needs to open a device file to operate, so what is the opened file? Here we need to create it manually (linux provides a method for the driver to automatically create device files, which will be mentioned in future articles, this article focuses on the file_operations structure, I hope you pay attention to not get lost) “mknod /dev/xxx c 200 0”, at this time, a device file named “xxx” will be created in the /dev directory, where 200 represents the major device number, which is the major device number passed in when we register_chrdev, and 0 represents the minor device number, which can be It is understood that there is an array of file_operations structures in the kernel, and this major and minor device number is used to find the array label of the file_operations we want. Open this file, and you can find the method in the file_operations we implemented.

Concrete sample code

Note: This code does not implement specific hardware-related functions, it is just a framework, including the driver entry and exit macros mentioned in the previous article, and the operation method of releasing a character device, please see:

#include <linux/ide.h>
#include <linux/module.h>

#define XXX_MAJOR 200 /* major device number */
#define XXX_NAME "xxx_test" /* device name */


static int xxx_open(struct inode *inode, struct file *filp)
{<!-- -->
//Usually return: 0 for success; others for failure
return 0;
}

static ssize_t xxx_read(struct file *filp, char __user *buf, size_t cnt, loff_t *offt)
{<!-- -->
//Usually return: the number of bytes read; less than 0, indicating that the read failed
return 0;
}

static ssize_t xxx_write(struct file *filp, const char __user *buf, size_t cnt, loff_t *offt)
{<!-- -->
//Usually return: the number of bytes written; less than 0, indicating that the write failed
return 0;
}

static int xxx_release(struct inode *inode, struct file *filp)
{<!-- -->
//Usually return: 0 for success; others for failure
return 0;
}

//This xxx_fops only lists some commonly used operations
static const struct file_operations xxx_fops = {<!-- -->
//owner The pointer to the module that owns the structure, generally set to THIS_MODULE
.owner = THIS_MODULE,
//The open function is used to open the device file. When the user program opens the device file, it will get a device descriptor and execute the open function specified by the driver (regardless of whether the driver has an open function, the user needs to open it)
.open = xxx_open,
// The read function is used to read the device file, and the function will be called when the application executes read
.read = xxx_read,
//write function is used to write (send) data to the device file, this function will be called when the application executes write
.write = xxx_write,
//release function is used to release (close) the device file, corresponding to the close function in the application
.release = xxx_release,
};

//Module initialization function, called when the module is loaded
static int __init xxx_init(void)
{<!-- -->
int ret = 0;
\t
//The main device number is 0, the device number is automatically assigned, and the return value is the assigned device number
ret = register_chrdev(XXX_MAJOR, XXX_NAME, &xxx_fops);
//The return value is negative, and the registration failed
if(ret < 0)
{<!-- -->
return -1;
}
\t
return 0;
}

//Module exit function, called when the module is unloaded
static void __exit xxx_exit(void)
{<!-- -->
// Unregister the character device driver
unregister_chrdev(XXX_MAJOR, XXX_NAME);
}

//Indicate the initialization function to the kernel and call it when the module is loaded
module_init(xxx_init);
//Indicate the exit function to the kernel and call it when the module is unloaded
module_exit(xxx_exit);

MODULE_LICENSE("GPL");
MODULE_AUTHOR("xiaoShuATao");

Note: The author’s level is limited, if there is any mistake, please point it out in time, I will modify it as soon as possible, thank you all.
Copyright Note: It can be reproduced and used freely, please indicate the source when reprinting.