Network Security Internet Technology Development Database Servers Mobile Phone Android Software Apple Software Computer Software News IT Information

In addition to Weibo, there is also WeChat

Please pay attention

WeChat public account

Shulou

What is the architecture of Linux character devices?

2025-01-17 Update From: SLTechnology News&Howtos shulou NAV: SLTechnology News&Howtos > Servers >

Share

Shulou(Shulou.com)05/31 Report--

This article mainly talks about "what is the architecture of Linux character devices". Interested friends may wish to take a look. The method introduced in this paper is simple, fast and practical. Now let the editor take you to learn "what is the architecture of Linux character devices?"

I. Linux equipment classification

For ease of management, the Linux system divides devices into three basic types:

Character equipment

Block equipment

Network equipment

Character device:

A char device is a device that can be accessed like a byte stream (like a file), and this feature is implemented by a character device driver. Character device drivers usually implement at least system calls to open, close, read, and write.

Character terminals (/ dev/console) and serial ports (/ dev/ttyS0 and similar devices) are two character devices that can well illustrate the abstract concept of "stream".

Character devices can be accessed through file nodes, such as / dev/tty1 and / dev/lp0. The only difference between these device files and ordinary files is that access to ordinary files can move the access location back and forth, while most character devices are a data channel that can only be accessed sequentially. However, there are also character devices with data area characteristics that can be accessed back and forth when accessing them. For example, the framebuffer is such a device that app can use mmap or lseek to access the entire captured image.

Execute ls-l under / dev, and you can see many created device nodes:

Character device file (type c), the device file has no file size, and is replaced by two numbers: major number 5 + secondary number 1.

Block device:

Like character devices, block devices are accessed through a file system node in the / dev directory. A block device, such as a disk, can hold filesystem. In most Unix systems, block devices can only transmit one or more complete blocks at a time, each containing 512 bytes (or data of a higher power of 2).

Linux allows app to read and write block devices like character devices, allowing any number of bytes of data to be passed at a time. Therefore, the difference between block devices and character devices lies only in the way the kernel manages data, that is, the software interface between the kernel and the driver, and these differences are transparent to the user. In the kernel, block drivers have a completely different interface than character drivers.

Block device file (type b):

Network devices:

Any network thing needs to be formed through a network interface, which is a device that can exchange data with other hosts. The interface is usually a hardware device, but it can also be a pure software device, such as a loopback interface.

The network interface is driven by the network subsystem in the kernel and is responsible for sending and receiving packets. Many network connections, especially those using the TCP protocol, are stream-oriented, but network devices are designed around the transmission and reception of data packets. The network driver does not need to know the information about each connection, it just needs to process the packet.

Because it is not a flow-oriented device, it is difficult to map the network interface to a node in the filesystem, such as / dev/tty1.

Unix still accesses network interfaces by assigning them a unique name (such as eth0), but there is no corresponding node for that name in filesystem. The communication between the kernel and the network device driver is completely different from the communication between the kernel and the character and the block driver. The kernel calls a set of packet-related functions socket, also called sockets.

View the network device using the command ifconfig:

Second, how is the character device architecture implemented?

In the world of Linux, everything is a file, and all hardware operations to the application layer will be abstracted into file operations. We know that if the application layer wants to access the hardware device, it must call the corresponding driver of the hardware. There are so many drivers in the Linux kernel, how can the application layer accurately call the underlying drivers?

Here we take a character device as an example to see how the application is associated with the underlying driver. The basics you must know:

1. In the Linux file system, each file is described by a struct inode structure that records all the information about the file, such as file type, access permissions, etc.

two。 In the Linux operating system, each driver will have a device file corresponding to it in the / dev directory of the application layer, and the file will have a corresponding primary and secondary number.

3. In the Linux operating system, each driver is assigned a primary device number, and the device number of the character device is stored in the struct cdev structure.

Struct cdev {struct kobject kobj; struct module * owner; const struct file_operations * ops;// interface function collection struct list_head list;// kernel linked list dev_t dev; / / device number unsigned int count;// number of secondary device numbers}

4. In the Linux operating system, each time a file is opened, the Linux operating system allocates a struct file structure in the VFS layer to describe the opened file. This structure is used to maintain file open permissions, file pointer offset values, private memory addresses, and other information.

Note:

Often we think that struct inode describes the static information of a file, that is, this information rarely changes. Struct file describes dynamic information, that is, when operating on files, the information in struct file often changes. Typically, the f_pos (recording the displacement of the current file) in the struct file structure changes the value of f_ops each time a normal file is read or written.

The relationships between these structures are shown in the following figure:

From the figure above, we can see that if you want to access the underlying device, you must open the corresponding device file. It is during this opening process that the Linux kernel associates the application layer with the corresponding driver.

1. When the open function opens the device file, you can know the type of device to operate next (character device or block device) according to the information described by the struct inode structure corresponding to the device file. A struct file structure is also assigned.

two。 According to the device number recorded in the struct inode structure, the corresponding driver can be found. Here, take character devices as an example. In the Linux operating system, each character device has a struct cdev structure. This structure describes all the information of the character device, the most important of which is the operation function interface of the character device.

3. Once the struct cdev structure is found, the Linux kernel records the memory space of the struct cdev structure in the i_cdev member of the struct inode structure for the first time. The address of the function operation interface recorded in the struct cdev structure is recorded in the f_op member of the struct file structure.

4. When the task is completed, the VFS layer returns a file descriptor (fd) to the application layer. This fd corresponds to the struct file structure. Next, the upper application can find the strut file through fd, and then find the function interface for operating the character device in the struct file.

Third, character-driven correlation function analysis

/ * *

* cdev_init ()-initialize a cdev structure

* @ cdev: the structure to initialize

* @ fops: the file_operations for this device

*

* Initializes @ cdev, remembering @ fops, making it ready to add to the

* system with cdev_add ().

, /

Void cdev_init (struct cdev * cdev, const struct file_operations * fops)

Features:

Initialize the cdev structure

Parameters:

@ cdev cdev structure address

@ fops the function interface address of the character device

Return value:

None

/ * *

* register_chrdev_region ()-register a range of device numbers

* @ from: the first in the desired range of device numbers; must include

* the major number.

* @ count: the number of consecutive device numbers required

* @ name: the name of the device or driver.

*

* Return value is zero on success, a negative error code on failure.

, /

Int register_chrdev_region (dev_t from, unsigned count, const char * name)

Features:

Register the device number of a range ()

Parameters:

@ from device number

Number of devices registered with @ count

Name of @ name device

Return value:

Success returns 0, failure returns error code (negative)

/ * *

* cdev_add ()-add a char device to the system

* @ p: the cdev structure for the device

* @ dev: the first device number for which this device is responsible

* @ count: the number of consecutive minor numbers corresponding to this

* device

*

* cdev_add () adds the device represented by @ p to the system, making it

* live immediately. A negative error code is returned on failure.

, /

Int cdev_add (struct cdev * p, dev_t dev, unsigned count)

Features:

Add a character device to the operating system

Parameters:

@ p cdev structure address

@ dev device number

@ count number of secondary devices

Return value:

Success returns 0, failure returns error code (negative)

/ * *

* cdev_del ()-remove a cdev from the system

* @ p: the cdev structure to be removed

*

* cdev_del () removes @ p from the system, possibly freeing the structure

* itself.

, /

Void cdev_del (struct cdev * p)

Features:

Remove a character device from the system

Parameters:

@ p cdev structure address

Return value:

None

Static inline int register_chrdev (unsigned int major, const char * name, const struct file_operations * fops) function: register or assign the device number, and register the fops to the cdev structure. If major > 0, the function is to register the master device number. If major=0, the function is to dynamically assign the master device number. Parameter: @ major: primary device number @ name: device name, execute the name displayed by cat / proc/devices @ fops: the interface pointer of the file system returns the value if major > 0 successfully returns 0, failure returns negative error code if major=0 successfully returns the primary device number, failure returns negative error code

This function encapsulates the initialization and registration of cdev, so you don't have to operate cdev yourself after calling the function.

The relative logout function is unregister_chrdev

Static inline void unregister_chrdev (unsigned int major, const char * name)

Fourth, how to write character device drivers

Referring to the figure above, the steps for writing a character device driver are as follows:

1. Implement module load and unload entry functions

Module_init (hello_init); module_exit (hello_exit)

two。 Apply for the main equipment number

Apply for the master number (used in the kernel to distinguish and manage different character devices)

Register_chrdev_region (devno, number_of_devices, "hello")

3. Create a device node

Create device node files (provide users with an operable-to-file interface-- open ()) there are two ways to create device nodes: manually and automatically. Create manually:

Mknod / dev/hello c 250 0

Automatically create device nodes

In addition to manually creating device nodes using the mknod command, we can also make use of the udev and mdev mechanisms of linux, and the busybox migrated on our ARM development board has a mdev mechanism, so we use the mdev mechanism to create device nodes automatically.

There is a sentence in the etc/init.d/rcS file:

Echo / sbin/mdev > / proc/sys/kernel/hotplug

This command is used to automatically create device nodes.

Udev is a tool that works in user space, it can update device files dynamically according to the status of hardware devices in the system, including device file creation, deletion, permissions and so on. These files are usually defined in the / dev directory, but can also be specified in the configuration file. Udev must have sysfs and tmpfs support in the kernel, sysfs provides device entry and uevent channel for udev, and tmpfs provides storage space for udev device files.

Udev runs in user mode, not in the kernel. Udev's initialization script creates a device node at system startup, and when a new device is inserted-- adding a driver module-- and registering new data on sysfs, udev innovates the new device node.

Note that udev achieves the purpose of customizing device files by modifying device files generated by the kernel or adding aliases. However, udev is a user-mode program that does not change kernel behavior. That is, the kernel still creates device files such as sda,sdb, while udev can distinguish different devices based on their unique information and generate new device files (or links).

For example:

If the driver module can export its own device number as a kernel parameter, there is a file called uevent in the sysfs file that records its value.

As you can see from the figure above, the uevent contains the values of the primary and secondary numbers, as well as the device name.

Start a udev program at the Linux application layer. The first time the program runs, it traverses the / sys directory, looks for the uevent files of each subdirectory, gets the information about creating the device node from these uevent files, and then calls the mknod program to create the device node in the / dev directory. When it's over, udev starts waiting for the kernel space event. We will talk about this equipment model in more detail later. It can be understood here that some function interfaces are provided in the Linux kernel, through which we can export the value of our device number in the sysfs file system, and after exporting the value, the kernel will report event to the application layer. At this point, udev knows there is work to do. After it receives the event, it reads the information corresponding to the event, and then starts to create the device node.

How do I create a device class?

Step 1: create an object of type class through the macro class _ create ()

/ * This is a # define to keep the compiler from merging different * instances of the _ _ key variable * / # define class_create (owner, name)\ ({\ static struct lock_class_key _ key;\ _ class_create (owner, name, & _ key) \}) Parameter: @ owner THIS_MODULE @ name class name return value can define a struct class pointer variable cls to accept the return value, and then determine whether it fails by IS_ERR (cls). If the macro returns 0 if it succeeds, the failure returns a non-9 value (the error code returned by failure can be obtained through PTR_ERR (cls)).

In the Linux kernel, devices are classified so that the same type of devices can be placed in the same directory. The implication of this function is that a class is created, such as:

Step 2: export our device information to user space

/ * * device_create-creates a device and registers it with sysfs * @ class: pointer to the struct class that this device should be registered to * @ parent, if any * @ devt: the dev_t for the char device to be added * @ drvdata: the data to be added to the device for callbacks * @ fmt: string for the device's name * * This function can be used by char device classes. A struct device * will be created in sysfs, registered to the specified class. * * A "dev" file will be created, showing the dev_t for the device, if * the dev_t is not 0. * If a pointer to a parent struct device is passed in, the newly created * struct device will be a child of that device in sysfs. * The pointer to the struct device will be returned from the call. * Any further sysfs files that might be required can be created using this * pointer. * Returns & struct device pointer on success, or ERR_PTR () on error. * * Note: the struct class passed to this function must have previously * been created with a call to class_create (). * / struct device * device_create (struct class * class, struct device * parent, dev_t devt, void * drvdata, const char * fmt,...)

Automatically create a device node usage instance:

Static struct class * cls; static struct device * test_device; devno = MKDEV (major,minor); cls = class_create (THIS_MODULE, "helloclass"); if (IS_ERR (cls)) {unregister_chrdev (major, "hello"); return result;} test_device = device_create (cls,NULL,devno,NULL, "hellodevice"); if (IS_ERR (test_device)) {class_destroy (cls) Unregister_chrdev (major, "hello"); return result;}

4 implement file_operations

Static const struct file_operations fifo_operations = {.owner = THIS_MODULE, .open = dev_fifo_open, .read = dev_fifo_read, .write = dev_fifo_write, .unloaded _ ioctl = dev_fifo_unlocked_ioctl,}

Open and release correspond to the open () and close () functions of the application layer. The implementation is relatively simple.

Just return 0 directly. The implementation of read, write and unloched_ioctrl functions needs to involve the data copy of user space and memory space.

In the Linux operating system, user space and kernel space are independent of each other. In other words, the kernel space cannot directly access the user space memory address, and similarly, the user space cannot directly access the kernel space memory address.

If we want to copy the user space data to the kernel space or copy the kernel space data to the user space, we must use the interface provided by the kernel.

1. Read interface implementation

User space-- > kernel space

The write API for character devices is defined as follows:

Ssize_t (* write) (struct file * filp, const char _ _ user * buf, size_t count, loff_t * f_pos) Parameter: filp: device file to be operated file structure pointer buf: user space buffer pointer to be written to read data count: number of data bytes to be read f_pos: data file location to be read, relocated according to the actual number of bytes written after writing is completed, return: the number of bytes actually written successfully, failure returns a negative value

If the operation is empty, it will cause the write system call to return negative EINVAL and return the number of bytes actually written normally.

Copying data from user space to kernel space requires the use of the copy_from_user function, which is defined in arch/arm/include/asm/uaccess.h.

Static inline int copy_from_user (void * to, const void _ _ user volatile * from,unsigned long n) Parameter: to: destination address (kernel space) from: source address (user space) n: number of bytes of data to be copied returned: 0 for success, and number of bytes of data that have not been copied successfully for failure

You can also use the get_ user macro:

Int get_user (data, ptr); parameter: data: kernel variable ptr of byte, halfword, word or double word type: user space memory pointer returns: 0 for success, non-0 for failure

2. Implementation of write interface

Kernel space-- > user space

The read API for character devices is defined as follows:

Ssize_t (* read) (struct file * filp, char _ _ user * buf, size_t count, lofft * f_pos) Parameter: filp: device file to be operated file structure pointer buf: user space buffer pointer to be written to read data count: number of data bytes to be read f_pos: location of the data file to be read, relocate according to the actual number of read bytes after reading is completed _ _ user: is an empty macro The main purpose of the display is to tell the programmer that the pointer variable it modifies stores the address of user space. Return value: the number of bytes actually read successfully, while the failure returns a negative value.

Note: if this operation is empty, the read system call will fail to return negative EINVAL and return the actual number of bytes read normally.

User space needs to use the copy_to_user function to read data from kernel space:

Number of static inline int copy_to_user (void _ user volatile * to, const void * from,unsigned long n): to: destination address (user space) from: source address (kernel space) n: number of bytes of data to be copied back: 0 for success, and number of bytes of data that were not copied successfully for failure

Insert a picture description here

You can also use the put_ user macro:

Int put_user (data, prt) parameter: data: kernel variable ptr of byte, halfword, word or double word type: user space memory pointer returns: 0 for success, non-0 for failure

In this way, we can implement read and write functions. Examples are as follows:

Ssize_t hello_read (struct file * filp, char * buff, size_t count, loff_t * offp) {ssize_t result = 0; if (count > 127) count = 127; if (copy_to_user (buff, data, count)) {result =-EFAULT;} else {printk (KERN_INFO "wrote% d bytes\ n", count); result = count;} return result } ssize_t hello_write (struct file * filp,const char * buf, size_t count, loff_t * f_pos) {ssize_t ret = 0; / / printk (KERN_INFO "Writing% d bytes\ n", count); if (count > 127) return-ENOMEM; if (copy_from_user (data, buf, count)) {ret =-EFAULT;} else {data [count] ='\ 0' Printk (KERN_INFO "Received:% s\ n", data); ret = count;} return ret;}

3. Implementation of unlocked_ioctl interface

(1) Why should xxx_ioctl be implemented?

Previously, we have implemented the read-write interface in the driver, through which we can complete the reading and writing of the device. But in many cases, our application layer engineers not only have to read and write data to the equipment, but also want to be able to control the equipment. For example, for serial devices, the driver layer not only needs to provide reading and writing to the serial port, but also needs to provide settings for the serial port baud rate, parity bit and termination bit. These configuration information needs to transfer some basic data from the application layer. It's just that the data types are different.

Through the xxx_ioctl function interface, it can provide the control ability of the device and increase the flexibility of the driver.

(2) how to implement the xxx_ioctl function interface?

Add xxx_ioctl function interface, the application layer can operate dev_fifo according to different commands through ioctl system call.

In kernel 2.6.35 and earlier, struct file_operations had a total of three ioctl: ioctl,unlocked_ioctl and compat_ioctl now only have unlocked_ioctl and compat_ioctl

In kernel 2.6.36, the ioctl function pointer in struct file_operations has been completely removed and replaced by unlocked_ioctl.

Kernel before 2.6.36

Long (ioctl) (struct inode node, struct file* filp, unsigned int cmd,unsigned long arg)

Kernel after 2.6.36

Long (* unlocked_ioctl) (struct file * filp, unsigned int cmd, unsigned long arg)

Parameter cmd: the command passed by applying the function ioctl

First, let's take a look at the correspondence between the ioctl of the application layer and the xxx_ioctl of the driver layer:

Analysis of ioctl parameters in Application layer

Int ioctl (int fd, int cmd,...); parameter: @ fd: get the file descriptor @ cmd when opening the device file: the second parameter: the command passed to the driver layer. When you need to pay attention, the command of the driver layer and the command of the application layer must unify @ the third parameter: "..." In C language, it is often understood as a variable parameter. The returned value is successful: 0 failed:-1, and set errno at the same time

Tips:

When we call the driver layer xxx_ioctl through ioctl, there are three options: 1: do not pass data to xxx_ioctl 2: pass data to xxx_ioctl in the hope that it can eventually write the data to the device (for example, setting the baud rate of the serial port) 3: call xxxx_ioctl to obtain the hardware parameters of the device (for example, the baud rate of the current serial device) Sometimes you need to pass data, and sometimes you don't need to pass data. In C language, it is impossible to realize function overloading. What can we do? Use "." To deceive the compiler, "." The original meaning is to transmit multiple parameters. What it means here is with or without a parameter. Parameters can pass the integer value or the address of a block of memory, and the kernel interface function must extract the corresponding information according to the actual situation.

Analysis of xxx_ioctl parameters of driver layer

Long (* unlocked_ioctl) (struct file * file, unsigned int cmd, unsigned long arg) Parameter: @ file: vfs layer is a structure created for the process of opening character device files to store dynamic information of files @ cmd: commands passed in user space can do different things according to different commands @ third parameter: data in user space, which may be an address value (an address is passed in user space) or a numeric value It is also possible that the return value is successful: 0 failed: negative value with error code

How to determine the value of cmd.

This value is mainly used to distinguish the type of command. Although I only need to pass any integer value, we try our best to make full use of the 32bite space in accordance with the kernel specification. If everyone has no rules, how can we become a square?

Now I'll take a look at how the cmd is designed in the Linux kernel.

The specific meaning is as follows:

By a command can be composed of four parts, each part of the bite is not exactly the same, to make a command need to write different numbers in different bit fields, Linux system has given us a package of macros, we just need to directly call the macro to design commands.

Insert a picture description here

Through the macros provided to us by the Linux system, we only need to specify three fields: device type, command serial number and data type when designing commands.

A command has been designed in the Linux system. You can check the Documentation/ioctl/ioctl-number.txt file in the Linux source code to see which commands have been used.

How do I check commands?

You can use the macro _ IOC_TYPE (nr) to determine whether the command type passed down by the application is correct

You can use the macro _ IOC_DIR (nr) to get whether the command is read or write, and then use the macro access _ ok (type,addr,size) to determine whether the memory address passed by the user layer is legal.

The method of use is as follows:

If (_ IOC_TYPE (cmd)! = DEV_FIFO_TYPE) {pr_err ("cmd% uEng bad magic 0x%x/0x%x.\ n", cmd,_IOC_TYPE (cmd), DEV_FIFO_TYPE); return-ENOTTY;} if (_ IOC_DIR (cmd) & _ IOC_READ) retouching accessory cards (VERIFY_WRITE, (void _ user*) arg,_IOC_SIZE (cmd)) Else if (_ IOC_DIR (cmd) & _ IOC_WRITE) retouching accessoriesook (VERIFY_READ, (void _ user*) arg,_IOC_SIZE (cmd)); if (ret) {pr_err ("bad access% ld.\ n", ret); return-EFAULT;}

5 register for cdev

After defining the file_operations structure, you can register the character device driver through the functions cdev_init () and cdev_add ().

Examples are as follows:

Static struct cdev cdev; cdev_init (& cdev,&hello_ops); error = cdev_add (& cdev,devno,1)

Note that if you use the function register_chrdev (), you don't have to do this because it already encapsulates cdev.

V. examples

Thousands of words, all summed up in this picture, you can learn against the corresponding levels.

VI. Examples

OK, now we can implement an example of a complete character device framework, including opening, closing, reading and writing, ioctrl, automatic creation of device nodes, and so on.

# include # include "dev_fifo_head.h" / / specified main device number # define MAJOR_NUM 250 / / your own character device struct mycdev {int len; unsigned char buffer [50]; struct cdev cdev;}; MODULE_LICENSE ("GPL"); / / device number static dev_t dev_num = {0}; / / Global gcd struct mycdev * gcd / / struct class * cls; / / get the data passed by the user and determine the number of registered devices based on it: static int ndevices = 1; module_param (ndevices, int, 0644); MODULE_PARM_DESC (ndevices, "The number of devices for register.\ n"); / / Open device static int dev_fifo_open (struct inode * inode, struct file * file) {struct mycdev * cd Printk ("dev_fifo_open success!\ n"); / / use the file private data pointer of struct file to save the struct mycdev structure pointer cd = container_of (inode- > iLighcdevje struct mycdev,cdev); file- > private_data = cd; return 0 } / / read device static ssize_t dev_fifo_read (struct file * file, char _ user * ubuf, size_t size, loff_t * ppos) {int n; int ret; char * kbuf; struct mycdev * mycd = file- > private_data; printk ("read * ppos:% lld\ n", * ppos); if (* ppos = = mycd- > len) return 0 / / request size > number of bytes remaining in buffer: read the actual number of bytes if (size > mycd- > len-* ppos) n = mycd- > len-* ppos; else n = size; printk ("n =% d\ n", n); / / read data from the location of the last file location pointer kbuf = mycd- > buffer + * ppos / / copy data to user space ret = copy_to_user (ubuf,kbuf, n); if (ret! = 0) return-EFAULT; / / update the value of the file location pointer * ppos + = n; printk ("dev_fifo_read success!\ n"); return n } / / write device static ssize_t dev_fifo_write (struct file * file, const char _ _ user * ubuf,size_t size, loff_t * ppos) {int n; int ret; char * kbuf; struct mycdev * mycd = file- > private_data; printk ("write * ppos:% lld\ n", * ppos) / / has reached the tail of buffer if (* ppos = = sizeof (mycd- > buffer)) return-1; / / request size > number of bytes remaining in buffer (write as much data as there is space) if (size > sizeof (mycd- > buffer)-* ppos) n = sizeof (mycd- > buffer)-* ppos; else n = size / / write data from the location of the last file location pointer kbuf = mycd- > buffer + * ppos; / / copy data to kernel space ret = copy_from_user (kbuf, ubuf, n); if (ret! = 0) return-EFAULT; / / update the value of the file location pointer * ppos + = n; / / update dev_fifo.len mycd- > len + = n Printk ("dev_fifo_write success!\ n"); return n;} / / linux kernel has abandoned the ioctl function pointer structure and replaced it with long dev_fifo_unlocked_ioctl (struct file * file, unsigned int cmd, unsigned long arg) {int ret = 0; struct mycdev * mycd = file- > private_data If (_ IOC_TYPE (cmd)! = DEV_FIFO_TYPE) {pr_err ("cmd% uEng bad magic 0x%x/0x%x.\ n", cmd,_IOC_TYPE (cmd), DEV_FIFO_TYPE); return-ENOTTY;} if (_ IOC_DIR (cmd) & _ IOC_READ) retouching accessory cards (VERIFY_WRITE, (void _ user*) arg,_IOC_SIZE (cmd)) Else if (_ IOC_DIR (cmd) & _ IOC_WRITE) retouching accessoriesok (VERIFY_READ, (void _ user*) arg,_IOC_SIZE (cmd)); if (ret) {pr_err ("bad access% ld.\ n", ret); return-EFAULT;} switch (cmd) {case DEV_FIFO_CLEAN: printk ("CMD:CLEAN\ n") Memset (mycd- > buffer, 0, sizeof (mycd- > buffer)); break; case DEV_FIFO_SETVALUE: printk ("CMD:SETVALUE\ n"); mycd- > len = arg; break; case DEV_FIFO_GETVALUE: printk ("CMD:GETVALUE\ n"); ret = put_user (mycd- > len, (int *) arg); break Default: return-EFAULT;} return ret;} / / device operation function interface static const struct file_operations fifo_operations = {.owner = THIS_MODULE, .open = dev_fifo_open, .read = dev_fifo_read, .write = dev_fifo_write, .unloaded _ ioctl = dev_fifo_unlocked_ioctl,} / / Module entry int _ _ init dev_fifo_init (void) {int I = 0; int n = 0; int ret; struct device * device; gcd = kzalloc (ndevices * sizeof (struct mycdev), GFP_KERNEL); if (! gcd) {return-ENOMEM } / / device number: primary device number (12bit) | Secondary device number (20bit) dev_num = MKDEV (MAJOR_NUM, 0); / / statically registered device number ret = register_chrdev_region (dev_num,ndevices, "dev_fifo") If (ret < 0) {/ / static registration failed, dynamic registration device number ret = alloc_chrdev_region (& dev_num,0,ndevices, "dev_fifo"); if (ret < 0) {printk ("Fail to register_chrdev_region\ n"); goto err_register_chrdev_region }} / / create the device class cls = class_create (THIS_MODULE, "dev_fifo"); if (IS_ERR (cls)) {ret = PTR_ERR (cls); goto err_class_create;} printk ("ndevices:% d\ n", ndevices); for (n = 0 and n < ndevices) N + +) {/ / initialize the character device cdev_init (& GCD [n] .cdev, & fifo_operations); / / add the device to the operating system ret = cdev_add (& GCD [n] .cdev, dev_num + nline 1); if (ret < 0) {goto err_cdev_add } / / Export device information to user space (/ sys/class/ class name / device name) device = device_create (cls,NULL,dev_num + njournal null, "dev_fifo%d", n); if (IS_ERR (device)) {ret = PTR_ERR (device); printk ("Fail to device_create\ n"); goto err_device_create }} printk ("Register dev_fito to system,ok!\ n"); return 0; err_device_create: / / remove the exported device information by removing for (I = 0 err_cdev_add I < n I + +) {device_destroy (cls,dev_num + I);} err_cdev_add: / / remove all the added for (I = 0) I < ntteri + +) {cdev_del (& GCD [I] .cdev);} err_class_create: unregister_chrdev_region (dev_num, ndevices); err_register_chrdev_region: return ret;} void _ exit dev_fifo_exit (void) {int I; / delete the device for (I = 0th I < ndevices) in the sysfs file system I + +) {device_destroy (cls,dev_num + I);} / delete the device class class_destroy (cls) in the system; / / remove the added character device for from the system (I = 0 X I < ndevices;i + +) {cdev_del (& GCD [I] .cdev) } / / release request device number unregister_chrdev_region (dev_num, ndevices); return;} module_init (dev_fifo_init); module_exit (dev_fifo_exit)

Contents of the header file:

Dev_fifo_head.h

# ifndef _ DEV_FIFO_HEAD_H # define _ DEV_FIFO_HEAD_H # define DEV_FIFO_TYPE'k' # define DEV_FIFO_CLEAN _ IO (DEV_FIFO_TYPE,0x10) # define DEV_FIFO_GETVALUE _ IOR (DEV_FIFO_TYPE,0x11,int) # define DEV_FIFO_SETVALUE _ IOW (DEV_FIFO_TYPE,0x12,int) # endif

Makefile:

Ifeq ($(KERNELRELEASE),) KERNEL_DIR? = / lib/modules/$ (shell uname-r) / build PWD: = $(shell pwd) modules: $(MAKE)-C $(KERNEL_DIR) KERNEL_DIR $(PWD) modules .PHONY: modules clean clean: $(MAKE)-C $(KERNEL_DIR) MIMO $(PWD) clean else obj-m: = dev_fifo.o endif

Application:

# include # include int main (int argc, const char * argv []) {int fd; int n; char buf [1024] = "hello word"; fd = open ("/ dev/dev_fifo0", O_RDWR); if (fd < 0) {perror ("Fail ot open"); return-1 } printf ("open successful, fd =% d\ n", fd); n = write (fd,buf,strlen (buf)); if (n < 0) {perror ("Fail to write"); return-1;} printf ("write% d bytes!\ n", n); n = write (fd,buf,strlen (buf)) If (n < 0) {perror ("Fail to write"); return-1;} printf ("write% d bytes!\ n", n); return 0;}

Test steps:

(1) load module

Sudo insmod hello.ko

(2) create a device node

Sudo mknod / dev/hello c 250 0

If the ability to automatically create device nodes is added to the code, do not perform this step.

(3) testing character equipment

Gcc test.c-o runsudo. / run at this point, I believe you have a deeper understanding of "what is the architecture of Linux character devices?" you might as well do it in practice. Here is the website, more related content can enter the relevant channels to inquire, follow us, continue to learn!

Welcome to subscribe "Shulou Technology Information " to get latest news, interesting things and hot topics in the IT industry, and controls the hottest and latest Internet news, technology news and IT industry trends.

Views: 0

*The comments in the above article only represent the author's personal views and do not represent the views and positions of this website. If you have more insights, please feel free to contribute and share.

Share To

Servers

Wechat

© 2024 shulou.com SLNews company. All rights reserved.

12
Report