Linux Device driver Note (iii) character device driver

Source: Internet
Author: User
Tags documentation

< A; main device number and secondary device number
Access to the character device is done through the device name in the file system. Those device names are simply nodes that are called file system trees, and they are typically located in the/dev folder.

Device files for character device drivers can be identified by ' C ' in the first column of the ls-l command output.

Block devices are the same in/dev, identified by the character ' B '
crw-rw---- 1 root root    253,   0 2013-09-11 20:33 usbmon0
CRW-RW---- 1 root root    253,   1 2013-09-11 20:33 usbmon1
crw-rw---- 1 root root   &N bsp;253,   2 2013-09-11 20:33 usbmon2
BRW-RW---- 1 root disk      8,   0 2013-09-11 20:3 3 SDA
BRW-RW---- 1 root disk      8,   1 2013-09-11 20:34 sda1
brw-rw---- 1 root dis K      8,   2 2013-09-11 20:33 sda2
        Main device number identifies the appropriate driver for the device, and the modern Linux kernel agrees that multiple drivers Master device number, but most devices are still organized according to the "one master device corresponding driver" principle.


The secondary device number is used by the kernel to correctly determine the device that the device file refers to. The ability to obtain a direct pointer to the kernel device via the secondary device number, or as an index to the device's local array. Either way. The kernel itself does not care about any other information about the secondary device number, except that the secondary device number is used to point to the device that the driver implements.
1. Internal expression of equipment designator
In the kernel, the dev_t type (defined in <linux/types.h>) is used to hold the device number----contains the main device number and the secondary device number. To obtain the main device number and the secondary device number of the dev_t. To use the macro major/minor:major defined in <linux/kdev_t.h> (dev_t Dev); MINOR (dev_t dev); instead, assume that you need to convert the master device number and the secondary device number to the dev_t type. Use Mkdev (int major, int minor);
2. Assigning and releasing device numbers
Before creating a character device, the first thing to do is to get one or more device numbers.

The necessary functions for this work are defined in <linux/fs.h>:
int Register_chrdev_region (dev_t first, unsigned int count, char *name);
A.first is the starting value for the range of device numbers to assign. This device number for first is often set to 0. However, it is not necessary for this function.
The B.count is the number of consecutive device numbers that are requested.
C.name is the device name associated with the number range, which appears in the/proc/devices and Sysfs today.
The return value of the d.register_chrdev_region is 0 when the allocation succeeds, and in the case of an error, a negative error code is returned. And you cannot use the requested numbering area.


Suppose you don't know which main device numbers the device will use. You will use Alloc_chrdev_region to dynamically assign the device number,

int alloc_chrdev_region (dev_t *dev, unsigned int firstminor, unsigned int count, char *name);
The A.dev is used only for output parameters. The first number of the allocated range is saved after a successful call.
The B.firstminor should be the requested first secondary device number to be used, which is generally 0.
The C.count and name parameters are the same as the Register_chrdev_region function.
Regardless of which method is used to assign the device number. Should release these device numbers when they are no longer being used, the release of the device number is required to use the following function void Unregister_chrdev_region (dev_t first, unsigned int count); Usually we call the Nregister_chrdev_region function in the purge function.
Before the user space program can access the above device number. The driver needs to connect the device number and the internal function. These intrinsic functions are used to implement the operation of the device.
3. Dynamic Allocation Master device number a part of the main device number has been statically assigned to most common devices.

A list of these devices can be found in the Documentation/devicex.txt file in the kernel source tree. For a driver. Handouts do not arbitrarily select a device number that is not currently used as the main device number, instead use the dynamic allocation mechanism to obtain the main device number.
The disadvantage of dynamic allocation is that the device node cannot be pre-created because the assigned primary device number is not guaranteed to always be consistent. To load a device driver that uses a dynamic master number, the call to Insmod can be replaced by a simple script that reads/proc/devices after calling Insmod to obtain the newly assigned master device number and then creates the appropriate device file. The best way to assign a master device number is to use dynamic allocation by default. At the same time, reserve the option to specify the main device number when loading or even compiling.


< two > Some important data structures
Most of the major driver operations involve three important kernel data structures, each of which is file_operations, file, and Inode.


1. File operation
The file_operations structure is used to establish the connection device number and driver operation. The structure is defined in <linux/fs.h>. Includes a set of function pointers. Each of the open files (file mentioned later) is associated with a set of functions. The operation of the driver is mainly used to implement the system call, named Open, read, etc., can feel the file when an "object". And the functional "method" that operates it. File_operations structures or pointers to such structures are called fops. Each field in the structure must point to a function in the driver that implements a specific action. For unsupported operations, the corresponding fields can be placed as null values. For each function, assuming that the corresponding field is assigned a null pointer, the kernel's detailed processing behavior is different.
In the file_operations. There are a number of parameters including a __user string, which is in fact a form of documentation, indicating that the pointer is a user-space address. Therefore, it cannot be directly referenced.
struct module *owner: A pointer to the module that owns the structure. The kernel uses this field to avoid uninstalling the module when its operation is in use. The member is initialized to This_module, which is defined in <linux/module.h>
Int (*open) (struct inode *, struct file *): This is the first action on a device file, but does not require that the driver must declare a phase
The method to be used.

Assuming this entry is NULL, the device's open operation is always successful, but the system does not notify the driver.


Int (*release) (struct inode *, struct file *): When the file structure is freed. This operation will be called. Similar to open, the release can also be set
Set to Null,release is not called when the process calls close every time. Only if the file structure is shared, release will wait until all copies are closed before it gets called.

Suppose you need to close a random copy to refresh those pending data. You should flush the method beforehand.
Int (*flush) (struct file *): A call to the flush operation occurs when a process shuts down a copy of the device file description descriptor, and it should run an operation that has not yet been completed on the device.

Assuming that flush is set to NULL, the kernel simply ignores requests from the user's program.


The unsigned int (*poll) (struct file *, struct poll_table_struct *):p Oll method is the back-end implementation of the three system calls of Poll/epoll and select. The poll method should return a bitmask that indicates whether non-clogging reads or writes are possible, and also provides information to the kernel that the calling process is put into hibernation until I/O becomes possible. Suppose the driver defines the poll method as null. The device will be considered readable and writable. And will not be blocked.
ssize_t (*read) (struct file *, char __user *, size_t, lofft_t *): Used to read data from the device. When the function pointer is given NULL, it causes the read system call to fail and returns-EINVAL. The function returns a non-negative value indicating the number of bytes successfully read.


ssize_t (*write) (struct file *, const char __user *, size_t, loff_t): Sends data to the device, assuming there is no such function. Write
The system call returns a-einval to the program, assuming that the return value is non-negative. Indicates the number of bytes successfully written.


2.file structure
The struct file defined in <linux/fs.h> is the second most important data structure used by a device driver.

Note that the file structure is not associated with file in the user-space program.

File is defined in the C library and does not go out of the kernel code today. The struct file is a kernel structure. Does not show up in the user program today.
The file structure represents an open document. It is created by the kernel at open. and pass to all functions that operate on the file, knowing the last Close function. After all instances of the file have been closed. The kernel releases this data structure. The most important members of the struct file are listed for example:
mode_t F_mode: File mode, which identifies whether a file is readable or writable through the fmode_read and fmode_write bits, because the kernel has checked access permissions before invoking the driver's read and write. Therefore, you do not have to check permissions for both methods. In the case where the file is opened without the appropriate access permission, the read and write operation to the file is rejected by the kernel, and the driver does not need to make additional inferences for this.


unsigned int f_flags: file flags, such as O_rdonly/o_nonblock/o_sync. To check whether a user request is a non-clogging operation. The driver needs to check the O_NONBLOCK flag, while the other flags are rarely used.

Note that checking read/write permissions should look at F_mode instead of F_flags. All of these flags are defined in <linux/fcntl.h> loff_t F_pos: The current read/write location. The loff_t is a 64-bit number. Suppose the driver needs to know the current position in the file and be able to read the value, but don't change it. Instead of working directly with File->f_pos, Read/write will update the position with the last pointer that they receive. An exception to this rule is the Llseek method, which is intended to alter the location of the file itself.
       struct file_operations *f_op: file-related actions. The kernel assigns a value to this pointer when it runs the open operation. This pointer is read later when these operations need to be handled. The value in File->f_op is never saved for convenience, that is to say. We are able to change the associated operation of a file whenever we need it, and after returning to the caller, the new method of operation will take effect immediately.
       void *private_data:open system calls set this pointer to null before invoking the driver's open method. The driver is able to use this field for whatever purpose or ignore this field.
      &NBSP;3.INODE structure
      The kernel uses inode structures to represent files internally. So it differs from the file structure. The latter represents an open file descriptive descriptor. to a single file. There may be many file structures that represent the opening of a descriptive descriptor, but they all point to a single inode structure. There are only two fields in the structure that are useful for writing drivers:
       dev_t I_rdev: An inode structure that represents a device file. This field includes the true device number
       struct Cdev *i_cdev:struct Cdev is the internal structure of the kernel that represents the character device. When the inode points to a character device file, the field is wrapped to a pointer to the struct CDEV structure.

In order to prevent the kernel version number of the upgrade caused by incompatibility problems, generally do not directly use I_rdev. Instead, use the following macro to obtain the device number:
unsigned int iminor (struct inode *inode); unsigned int imajor (struct inode *inode);


< three > registration of character devices
Before the kernel invokes the operation of the device, one or more struct CDEV structures must be assigned and registered.

For this Must include <linux/cdev.h>, which defines the structure and the associated auxiliary functions:
There are two ways to assign and initialize a struct CDEV structure:
struct Cdev *my_cdev = Cdev_allo ();
void Cdev_init (struct cdev *cdev, struct file_operations *fops);
My_cdev->ops = &my_fops;
The field of another struct Cdev needs to be initialized, similar to the file_operations structure, and the struct Cdev has a full-person field. Should be set to This_module.


After the CDEV structure is set up. The final step is to tell the kernel about the structure with the following call:
int Cdev_add (struct cdev *dev, dev_t num, unsigned int count);

Dev is a CDEV structure, and NUM is the first device number corresponding to the device, and count is the number of device numbers that should be associated with the device, and count often takes 1. When using Cdev_add, you need to be aware of:

A. This call may fail.

Assuming that it returns a negative error full, the device is not added to the system.

B. Just to Cdev_add returned, the operation of the device is called by the kernel. So. When the driver is not fully prepared for the benefit of the operation on the device. You cannot call Cdev_add.

The device to remove a character from the system. Make for example the following call: void Cdev_del (struct Cdev *dev), after the dev is passed to the Cdev_del function. You should not visit the CDEV structure again.


The way to get up early:

The classic way to register character device drivers: int register_chrdev (unsigned int major, const char *name, struct file_operations *fops); assuming Register_ The Chrdev function, the correct function to remove its own device from the system is: int Unregister_chrdev (unsigned int major, const char *name);


< four >open and release
1.open method: The Open method provides the ability for the driver to initialize, thus preparing for subsequent initialization of the operation. In most of the drivers. Open should be completed such as the following work:

A. Checking for device-specific errors

B. Assume that the device was first opened. to initialize it.

C. assumptions are necessary. Update F_op pointer

D. Assigning and filling in data structures placed in Filp->private_data

The first thing to do is to determine the detailed device to open, the Open method prototype is: Int (*open) (struct inode *inode,struct file *filp), in which the inode number includes the information we need in its I_cdev field, The CDEV structure that we have previously set.

The only problem is that we usually do not need to cdev the structure itself, but want to get the SCULL_DEV structure including the CDEV structure.

By defining the CONTAINER_OF macro implementation in <linux/kernel.h>: container_of (pointer, Container_type, Container_field); This macro requires a pointer to a Container_field field. The field is included in the structure of the Container_type type, and then returns a structure pointer that includes the field.
struct Scull_dev *dev; /*device information*/
dev = container_of (inode->i_cdev, struct scull_dev, Cdev);
Filp->private_data = Dev; /*for other methods*/
Another way to determine which device to open is to check the secondary device number that is saved in the inode structure.
2.release method: The Release method is exactly the same as open, this device method should complete the following tasks:

A. Release all content that is assigned by open and saved in Filp->private_data.

B. Turn off the device on the last side of the shutdown operation


< five >read and write
The Read and write methods complete a similar task, which is to copy data to the application space, or vice versa, to copy data from the application space.
ssize_t Read (struct file *filp, char __user *buff, size_t conut, loff_t *OFFP);
ssize_t Write (struct file *filp, char __user *buff, size_t conut, loff_t *OFFP);
The parameter FILP is a file pointer. The count is the length of the data requested for transmission. The number buff is a buffer that points to the user space. This buffer either protects the data to be written or is an empty buffer that holds the read-in data. The last OFFP is a pointer to the "Long offset type" object. This object indicates the location of the user's access operation in the file.
It should be noted that the buff parameters of the read and write methods are pointers to user space, so the kernel code cannot directly reference the contents. There are several reasons for such limitations to occur, such as the following:
A. The pointer to user space may be invalid when executed in kernel mode, depending on the architecture that the driver executes or the configuration of the kernel.

The address may not be mapped to kernel space at all, or may point to some random data.
B. Even if the pointer represents the same thing in kernel space, the memory of the user space is paged, and the memory involved may not be in RAM at all when the system call is invoked. A direct reference to user space memory causes a page fault, which is not agreed to by the kernel code, and may result in a "oops" that causes the death of the process calling the system call.
C. The pointers we are discussing may be provided by the user program. The program may be defective or a malicious program. Assuming that the driver blindly refers to a user-supplied pointer, it causes the system to appear open backdoors. This allows user-space programs to access or overwrite memory in the system. If the reader does not intend to compromise the security of the user's system because of its own driver, never directly reference the user space pointer.
What the read and write codes do is to make a copy of the whole piece of data between the user's address space and the kernel address space.

The implementation core of the read and write methods is:
unsigned long copy_to_user (void __user *to, const void *from, unsigned long count);
unsigned long copy_from_user (void __user *to, const void *from, unsigned long count);

These two functions are not limited to copying data between kernel space and user space. They also check that the pointer to the user space is valid.

Assume that the pointer is invalid. There is no copy of the data; Assuming an invalid address is encountered during the copy process, only part of the data is copied. In both cases, the return value also requires a copy of the memory amount value. As for the actual device method, the task of the Read method is to copy the data from the device to the user space. The Write method copies the data from the user space to the device. Each read or write system call requests a certain number of bytes to be transmitted, but the driver does not limit the transfer of small amounts of data.

No matter how much data is transferred. The file location represented by *OFFP should be updated to reflect the current file location after successful completion of the new system call. When an error occurs, both the read and write methods return a negative value that is greater than or equal to 0 to tell the caller how many bytes were successfully transferred.

Assuming an error occurs after the partial data is transferred correctly, the return value must be the number of bytes successfully transferred. Although kernel functions represent errors by returning negative values, and the return value indicates the type of error, the execution of a program in user space sees the clock as the return value of-1.


1.read Method:
The calling program interprets the return value of Read for example the following:
A. Assume that the return value equals the Count parameter passed to the read system call. Indicates that the number of bytes requested has been successfully transmitted.
B. Assume that the return value is positive. But smaller than count, it means that only some of the data is successfully delivered.

There are a number of reasons why this can happen for different devices. Most of the cases. The program will read the data again.
C. Assuming that the return value is 0, the end of the file has been reached.
D. A negative value means an error has occurred that indicates what error occurred. The error code is defined in <linux/errno.h>.


2.write Method:

like read, write can transmit less than the requested amount of data, such as the following return value rules:
A. Assuming that the return value equals count, the requested number of bytes is passed
B. Assuming that the return value is positive, but less than count, only some of the data is transferred. The program may try to write the rest of the data again.


C. Assuming a value of 0, meaning that nothing is written, this result is not an error. And there's no reason to return an error code.

D. Assignment means an error has occurred, same as read. Valid error codes are defined in <linux/errno.h>.

Linux Device driver Note (iii) character device driver

Contact Us

The content source of this page is from Internet, which doesn't represent Alibaba Cloud's opinion; products and services mentioned on that page don't have any relationship with Alibaba Cloud. If the content of the page makes you feel confusing, please write us an email, we will handle the problem within 5 days after receiving your email.

If you find any instances of plagiarism from the community, please send an email to: info-contact@alibabacloud.com and provide relevant evidence. A staff member will contact you within 5 working days.

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.