An implementation method of an operating system abstraction layer applied to driver development
By building an operating system abstraction layer between the kernel and the driver, encapsulating kernel capabilities and providing a stable interface, the driver compatibility problem caused by kernel version upgrade is solved, and the driver compatibility is achieved.
Patent Information
- Application Number
- CN202510245483.X
- Authority / Receiving Office
- CN · China
- Patent Type
- Patents(China)
- Current Assignee / Owner
- Filing Date
- 2025-03-04
- Publication Date
- 2025-06-13
- Estimated Expiration
- 2045-03-04
AI Technical Summary
Due to the KABI instability caused by kernel version upgrade, driver binary files compiled by old versions cannot run directly on the new version of the kernel, and need to be recompiled to ensure compatibility.
By building an operating system abstraction layer between the kernel and the driver, encapsulating kernel capabilities and providing a stable interface, the driver is written by calling the abstraction layer interface instead of directly calling the kernel KAPI functions.
It ensures that the driver can still run compatiblely after the kernel version is upgraded, avoids the process of recompiling and testing the driver after each kernel upgrade, and reduces the development workload of device manufacturers.
Smart Images

Figure CN119739390B_ABST
Abstract
Description
Technical Field
[0001] The present invention relates to the field of kernel technology, and specifically provides a method for implementing an operating system abstraction layer applied to driver development. Background Art
[0002] A Linux kernel driver is a piece of code that can be loaded into the operating system kernel, and is usually used to manage hardware devices and provide an interface between the operating system and the hardware. These drivers provide the operating system with the ability to interact with hardware devices, enabling the operating system to identify, manage, and control hardware devices. In the Linux system, kernel drivers exist in the form of modules and can be dynamically loaded into the kernel or unloaded from the kernel. This modular design enables the Linux system to flexibly support various hardware devices without having to recompile the entire kernel every time a hardware device is added or replaced.
[0003] KABI (Kernel Application Binary Interface) refers to the kernel application binary interface, that is, the stable binary interface provided by the kernel for kernel module programs. Ensuring the stability of KABI is very important for ensuring module compatibility when the kernel version is upgraded. KABI is designed to enable kernel modules to interact smoothly with the Linux kernel, especially when the kernel is upgraded.
[0004] With the rapid iterative upgrade of the kernel version, each version of KABI (Kernel ABI, that is, the kernel application binary interface) is not compatible. Since the driver binary file depends on the stable interface provided by KABI to interact with the kernel, this leads to a significant problem that the driver binary file compiled in the old version cannot run directly on the new version of the kernel and must be recompiled on the new kernel. Therefore, after each kernel upgrade, additional time and resources need to be invested to recompile and test the driver program to ensure its compatibility with the new kernel. This situation undoubtedly increases the development workload of device manufacturers. Summary of the Invention
[0005] In order to overcome the above defects, the present invention is proposed to provide a technical solution to solve the problem of incompatibility of driver binary files on different kernel versions.
[0006] The present invention provides a method for implementing an operating system abstraction layer applied to driver development, including the following steps:
[0007] Define corresponding abstract layer structure bodies and abstract layer interface functions according to different basic capabilities, or define corresponding abstract layer structure bodies, abstract layer interface functions, and abstract layer callback functions to encapsulate the basic capabilities of the operating system, provide stable interfaces, and export them for use by drivers;
[0008] Provide general data structures, implement their operation methods, and export them for use by drivers;
[0009] Implement an abstract layer device driver subsystem to provide a stable framework for writing driver programs;
[0010] Implement a set of platform peripheral drivers and provide them to other driver modules in the form of services or exported interfaces;
[0011] Implement a general user-space driver access library to communicate with the driver through library functions.
[0012] Furthermore, the steps for defining the abstract layer structure body include:
[0013] Define an abstract layer structure body named "OsalXXX" according to the basic capabilities, where Osal is a predefined prefix and XXX represents the current basic capabilities;
[0014] In the abstract layer structure body, define a pointer member variable realXXX of an empty type to associate with a specific kernel structure instance in subsequent operations, where real is a predefined prefix;
[0015] Implement an abstract layer structure body initialization function to associate the kernel structure body and the abstract layer structure body, and save the kernel structure body variable as the pointer member variable of the abstract layer structure body;
[0016] Implement an abstract layer structure body operation function, where the first parameter of the function is the pointer variable of the abstract layer structure body, and the subsequent parameters correspond to the parameters of specific kernel functions;
[0017] Implement an abstract layer structure body destruction function to perform anti-initialization operations on the associated kernel structure body and release the memory.
[0018] Furthermore, the implementation of an abstract layer structure body initialization function to associate the kernel structure body and the abstract layer structure body, and save the kernel structure body variable as the pointer member variable of the abstract layer structure body, further includes:
[0019] When the basic capabilities involve multiple structure types or auxiliary variables, integrate the relevant structure types and auxiliary variables into an intermediate structure body, and then save the instance of the intermediate structure body type into the pointer member variable of the abstract layer structure body.
[0020] Furthermore, the steps for defining the abstract layer interface function include:
[0021] Define the abstract layer interface function using a unified naming convention. The interface function names are prefixed with Osal, and the parameters of the function are of basic data types or abstract layer structure types;
[0022] The function body implementation includes the specific logic of the abstract layer and calls the kernel function to implement specific operations;
[0023] Export the kernel symbol using EXPORT_SYMBOL.
[0024] Furthermore, the steps for defining the abstract layer callback function include:
[0025] Define an abstract layer callback function pointer type, which is used for the driver to register a callback function with the abstract layer;
[0026] Implement a transit callback function, which is triggered during system calls and calls the corresponding abstract layer callback function inside the function;
[0027] Implement the abstract layer callback registration function, which is used to register the transit callback function into the system and bind the transit callback function with the abstract layer callback function;
[0028] Implement the abstract layer callback unregistration function, which is used to call the kernel's unregistration function and cancel the association between the transit callback function and the abstract layer callback function.
[0029] Furthermore, the general data structure includes linked lists, queues, red - black trees, and a general data buffer structure for data communication between the user space and the kernel space. By providing a set of operation functions, the functions of writing and reading basic data types to / from the buffer are realized.
[0030] Furthermore, the abstract layer device driver subsystem provides registration services, deregistration services, event sending, and message dispatching callback interfaces for driver development. The abstract layer device driver subsystem provides binding services, unbinding services, message sending, registration of event listeners, and deregistration of event listeners interfaces for application development.
[0031] The driver registers with the abstract layer device driver framework in the form of a service. The application establishes a connection with the driver development through the service name, sends messages to the driver service as a client, and the driver replies with the result to the application after processing the messages.
[0032] Further, the platform peripheral driver set includes GPIO, I2C, SPI, UART, ADC, DAC, RTC, and DMA peripheral drivers. By registering a service for each driver and defining a set of operation function sets according to different device types, the function sets internally access the corresponding IO functions of the kernel, and other device driver programs obtain the corresponding operation functions through the service interface and implement the IO operation of the underlying data by sending messages to the service.
[0033] Further, the general user space driver access library encapsulates the service binding, data sending, and event listening functions into a library file, and the user space program uses the library function to send data to the server and register a listener.
[0034] The working principle and beneficial effects of the present invention:
[0035] In implementing the technical solution of the present invention, an operating system abstraction layer is constructed between the kernel and the driver program, and the kernel capabilities required by the driver are encapsulated into stable interfaces of KABI (Application Binary Interface). The driver program is written by calling the interfaces provided by these abstraction layers instead of directly calling the system's KAPI functions. When the kernel is upgraded, since the KABI of the abstraction layer remains stable, the driver module compiled on the old version of the kernel can directly run on the new version of the system, thus ensuring the compatibility and stability of the driver program. Effectively solves the compatibility problem of driver programs that may be caused by kernel upgrades. BRIEF DESCRIPTION OF THE DRAWINGS
[0036] Referring to the accompanying drawings, the disclosure of the present invention will become more understandable. It is easy for those skilled in the art to understand that these drawings are only for illustrative purposes and are not intended to limit the protection scope of the present invention. In addition, similar numbers in the figures are used to represent similar components, where:
[0037] Figure 1 is a schematic structural diagram of the interaction of the abstraction layer in the operating system in the present invention;
[0038] Figure 2 is a schematic flowchart of implementing the abstraction layer structure in the abstraction layer in the present invention;
[0039] Figure 3 is a schematic flowchart of implementing the abstraction layer interface function in the abstraction layer in the present invention;
[0040] Figure 4 is a schematic flowchart of implementing the abstraction layer callback function in the abstraction layer in the present invention. DETAILED DESCRIPTION OF THE EMBODIMENTS
[0041] Some embodiments of the present invention will be described below with reference to the accompanying drawings. Those skilled in the art should understand that these embodiments are only used to explain the technical principle of the present invention and are not intended to limit the protection scope of the present invention.
[0042] In the present invention, the main reason for KABI instability is that the exported kernel structures are too complex, and the usage rights of most members of these structures are within the kernel, not in the driver. As the kernel functions are iteratively upgraded, these structure members will change (add or delete some member variables), and these changes will only affect the kernel in many cases and will not affect the driver because the driver does not use the newly added or deleted member variables of the changed structure. However, at this time, the KABI has changed, resulting in the driver having to be recompiled. The method to avoid KABI changes is to export more concise structures as much as possible, and do not export the member variables that are only needed by the kernel to the driver. Therefore, the main idea of the system abstraction layer design is not to use kernel structures outside functions (including as function formal parameters), and use functions to implement when operating on structure members.
[0043] A method for implementing an operating system abstraction layer applied to driver development in this embodiment aims to enable the driver binary file to work properly on different kernel versions. It mainly includes the following steps S10 - step S50. Figure 1 It is a schematic diagram of the interaction structure of the abstraction layer in the operating system of the present invention. The abstraction layer implemented after executing steps S10 - S50 is as Figure 1 shown.
[0044] Step S10: According to different basic capabilities, the operating system abstraction layer defines corresponding abstraction layer structures and abstraction layer interface functions for them, or defines corresponding abstraction layer structures, abstraction layer interface functions, and abstraction layer callback functions for them, encapsulates the operating system basic capabilities, provides stable interfaces, and exports them to the driver (i.e., Figure 1 the "driver program" in Figure 1 below, and the "driver" appearing below also corresponds to Figure 1 the "driver program" in
[0045] In one embodiment, the operating system basic capabilities include thread usage, process synchronization, memory usage, interrupt usage, IO mapping, file access, and timer usage.
[0046] Among them, thread usage includes thread creation, thread binding (binding a thread to a specified CPU core), thread startup, and thread destruction. Process synchronization includes mutex locks, spin locks, semaphores, and work queues. Memory usage includes memory allocation and memory recycling. Interrupt usage includes interrupt mapping, interrupt application, interrupt cancellation, interrupt enabling, and interrupt disabling. IO mapping includes mapping IO addresses to the virtual address space, unmapping, reading IO addresses, and writing to IO addresses. File access includes opening a file, reading the content of a file, writing data to a file, moving the read / write position, and closing the file. Timer usage includes creating a timer, starting a timer, and destroying a timer.
[0047] In this embodiment, not all basic capabilities require the definition of three types of objects. The abstract layer structure and the abstract layer interface functions are necessary, and the abstract layer callback functions are optional according to the requirements of the basic capabilities. That is to say, each basic capability corresponds to a function of the kernel. Around this function, the kernel will define a series of structures and functions to support the basic capabilities of the operating system, and the abstract layer hides the implementation details of the kernel structures. Taking thread usage as an example, only one structure needs to be created, and the corresponding abstract layer interface functions and abstract layer callback functions for creation, binding, startup, and destruction are defined.
[0048] In one implementation, as Figure 2 shown, for each basic capability, the construction of the abstract layer structure is carried out according to the following steps:
[0049] 1.1 Define an abstract layer structure named "OsalXXX" according to the basic capability, where Osal is a predefined prefix and XXX is the representation of the current basic capability. For example, for timer usage, the abstract layer structure is named OsalTimer.
[0050] 1.2 In the abstract layer structure, define a pointer member variable realXXX of void type, which is used to associate with a specific kernel structure instance in subsequent operations, where real is a predefined prefix.
[0051] The driver does not care about the specific type pointed to by this pointer member variable because the instantiation and use of the actual type are both completed through specific functions. Taking the abstract layer structure type named OsalTimer as an example, it only contains a pointer member variable named realTimer, and its type is void .
[0052] Among them, a pointer represents the address of a variable in memory, and a pointer member variable represents a member of a structure type, which is of pointer type. For example: int a, int b; a is an integer variable, indicating that an integer is stored. b is an integer pointer variable, indicating that the value stored is a memory address. Define a structure A as struct A{int a; int b;}, which contains two member variables. a is an integer member variable, and b is an integer pointer member variable. struct A aa, struct A bb; aa is of type struct A, and bb is a pointer of type struct A.
[0053] 1.3 Implement an abstract layer structure initialization function OsalXXXCreate. This initialization function is used to associate the kernel structure and the abstract layer structure, and save the kernel structure variable as the pointer member variable of the abstract layer structure.
[0054] Taking the timer as an example, the corresponding initialization function is named OsalTimerCreate. The first parameter of the initialization function is of the pointer type of the abstract layer structure OsalTimer. In the initialization function, the realTimer pointer member variable is obtained through the first parameter. The goal of this initialization function is to associate the realTimer pointer member variable with the timer kernel structure.
[0055] In one implementation, when the kernel capabilities corresponding to the basic capabilities involve multiple structure types or auxiliary variables, the relevant structure types and auxiliary variables need to be integrated into an intermediate structure, and then an instance of this intermediate structure type is saved into the pointer member variable of the abstract layer structure (corresponding to Figure 2 "Instantiating a null pointer into a specific kernel structure" above).
[0056] The encapsulation of the timer belongs to the above-mentioned situation that requires auxiliary variables. It is necessary to define an intermediate structure to integrate the kernel structure types and auxiliary variables related to the timer. This intermediate structure type is named osal_timer, and it contains the following members:
[0057] (1) A timer_list kernel structure variable. In the kernel, the timer is represented by the timer_list structure, which is affected by the system KABI;
[0058] (2) An integer variable used to save the timeout time;
[0059] (3) A pointer to a timer timeout handling function.
[0060] In the OsalTimerCreate function, a pointer variable of the osal_timer intermediate structure type is first created, and memory is allocated for it through the dynamic memory allocation function. The members of the osal_timer intermediate structure (the above three) are then initialized through the parameters of OsalTimerCreate. Then, the pointer variable is forcibly converted into a void pointer type, and finally, it is saved into the realTimer pointer member variable of the first parameter (OsalTimer structure type variable) of the OsalTimerCreate function. After abstraction, since realTimer in OsalTimer is defined as a void pointer member variable, the kernel structure is not exposed. OsalTimerCreate uses the pointer of the OsalTimer abstraction layer structure as a parameter, hiding the call of the kernel function inside the function, and also not exposing the kernel function, so it is not affected by the system KABI.
[0061] 1.4 Implement an operation function for the abstraction layer structure, which is used to implement the specific functions of the kernel structure. The first parameter of the function is the pointer member variable of the previously defined abstraction layer structure, and the subsequent parameters correspond to the parameters of the specific kernel function. These parameters must be of basic types or the structure types defined by the abstraction layer. First, the void pointer member variable is taken out from the first parameter, and then it is forcibly converted into the corresponding kernel structure object (corresponding to Figure 2 "Taking out the kernel structure through the void pointer of the abstraction layer structure inside the operation function"), and then, according to the functions to be implemented, using the kernel structure object as a parameter, the specific kernel function is called for implementation (corresponding to Figure 2 "Calling the kernel API function as a parameter to implement the specific function").
[0062] Taking the timer as an example, the operation function to start the timer can be defined as OsalTimerStart. The member variable realTimer is taken out through the pointer of the first parameter OsalTimer and forcibly converted into the osal_timer intermediate structure pointer type (the kernel structure is saved inside the osal_timer intermediate structure. In the case of a timer that requires auxiliary variables, realTimer does not directly point to the corresponding kernel structure timer_list, but the custom intermediate structure osal_timer). Then, the pointer member variable of its timer_list structure type is taken out, and the add_timer kernel function is called, using this timer_list structure type pointer variable as a parameter to start the kernel timer.
[0063] 1.5 Implement an abstract layer structure destruction function OsalXXXDelete. In the destruction function, it is necessary to de-initialize the associated kernel structure and release the memory allocated during initialization.
[0064] For the abstract layer timer destruction function, its first parameter is the pointer member variable of the previously defined abstract layer structure, and the subsequent parameters correspond to the parameters of the specific kernel function. These parameters must be of basic types or the structure types defined by the abstract layer. Taking the timer as an example, the timer destruction function is named OsalTimerDelete. The realTimer pointer member variable is retrieved through the first parameter OsalTimer pointer, and the member of the timer_list kernel structure type is retrieved through realTimer, and then the kernel function is called to de-initialize the timer_list.
[0065] In one implementation, as Figure 3 shown, the steps to define the abstract layer interface functions in the operating system abstract layer are as follows:
[0066] 2.1 Define the abstract layer interface functions using a unified naming convention. The function names are prefixed with Osal, and the parameters of the functions are of basic data types (basic data types in C language, integer type, character type, floating-point type) or abstract layer structure types, so as to avoid the impact of KABI on the abstract layer interface functions. For example, the interface function for dynamically allocating memory void OsalMemAlloc(size_t size);
[0067] In this embodiment, the abstract layer interface functions and the abstract layer structures jointly implement a basic function of the kernel. Similar to object-oriented programming, the abstract layer structures are equivalent to attributes, and the abstract layer interface functions are equivalent to methods. Since C language does not have the concept of classes, the first parameter of the abstract layer interface functions must all be pointer types of the corresponding abstract layer structures. The abstract layer can be understood as a Java virtual machine. The structures and interface functions exposed by the abstract layer are equivalent to bytecodes, and the kernel structures and functions are equivalent to machine codes. The kernel structures and functions may change during the version iteration process, resulting in changes in KABI, but the structures and functions of the abstract layer will not change with the kernel version. Only when the version is upgraded, it needs to be re-"translated" into the new kernel structures and functions.
[0068] 2.2 Function body implementation. Specifically, it includes two parts. One part is to implement the specific logic of the abstraction layer. The specific logic refers to the logic that has nothing to do with calling kernel functions, that is, the logic code that has nothing to do with kernel functions. For example, when allocating memory, different kernel functions can be called according to the size of the memory to be allocated, and some memory statistics functions can also be added. The allocation and release of memory can be recorded in a global data structure as a reference data for subsequent software debugging. The other part is to call kernel functions to implement specific operations (corresponding to Figure 3 "call kernel functions to implement interface functions" in Figure 3 ). For example, when applying for memory, kmalloc or vmalloc is called to allocate memory. By doing so, additional logic of the operating system abstraction layer can be added when allocating and releasing memory, and kmalloc or vmalloc can be selected according to the memory size. More importantly, the driver does not directly call the kmalloc and vmalloc functions. During the kernel version upgrade process, if the parameters or function names of the corresponding kernel functions change, only the implementation part of the abstraction layer function needs to be modified, while the external interface remains unchanged.
[0069] 2.3 Use EXPORT_SYMBOL to export kernel symbols so that the driver can call the abstraction layer functions when compiled as a module.
[0070] This is an operation of the kernel. If a function needs to be used by an external module, the symbol needs to be exported using the EXPORT_SYMBOL macro. The code of the abstraction layer is compiled together with the kernel code, and the driver is compiled as a module. If the driver needs to call the functions of the abstraction layer, the symbols of the abstraction layer functions need to be exported.
[0071] In one implementation, as Figure 4 shown, are the steps to define the abstraction layer callback function in the operating system abstraction layer.
[0072] It should be noted in advance that not all kernel functions require callback functions. The difference between the abstraction layer callback function and the abstraction layer interface function is that the abstraction layer interface function is actively called by the user program, and the kernel does not know the type of the abstraction layer callback function. The kernel can only recognize the type of the callback function defined in the kernel, and the callback function type of the kernel cannot be directly exposed to the driver. Therefore, a transit callback function needs to be implemented. The transit callback function is a type that the kernel can recognize, and the corresponding abstraction layer callback function is called inside the function.
[0073] As Figure 4 shown, the steps to define the abstraction layer callback function in the operating system abstraction layer include:
[0074] 3.1 Define an abstract layer callback function pointer type. This pointer type is used for the driver to register callback functions with the abstract layer. The parameters and return value of this function pointer type do not include kernel structure types. For example, for a kernel timer timeout, it can be defined as: typedef void ( OsalTimerFunc)(uintptr_t arg);
[0075] 3.2 Implement a transit callback function, which is a type recognizable by the kernel and is triggered during system calls. Inside the function, the corresponding abstract layer callback function is called. The transit callback function and the abstract layer callback function have different parameters. The transit callback function contains parameters of kernel structure data types, and these parameters need to be processed or converted to basic types inside the transit callback function. Obtain the abstract layer callback function pointer associated with the transit callback function and initiate a function call (see 3.3 for the association method), and these parameters will be passed to the abstract layer callback function. For example, for a kernel timer timeout callback, the callback function is set through the timer_setup (refer to kernel-6.6) function, and the type of the callback function is: void ( func)(struct timer_list ).
[0076] Obviously, if the driver directly uses the above func type to define the callback function, there is a risk because its parameter is of the timer_list type, which is a kernel structure type. Therefore, the func type callback function only serves as a transit and is responsible for triggering system calls. Inside the transit callback function, the above-defined abstract layer callback function is called again. Note that the parameters of the transit callback function and the abstract layer callback function are different. The instance of the abstract layer callback function is implemented by the specific driver, so its parameters cannot include kernel structure types.
[0077] 3.3 Implement an abstract layer callback registration function to register the transit callback function with the system and bind the transit callback function to the abstract layer callback function. In this way, when the transit callback function is called, the corresponding abstract layer callback function pointer can be found. The binding method can be achieved by embedding the transit callback function and the abstract layer callback function into a larger structure, and the instance of this structure is associated with the member of the corresponding abstract layer structure.
[0078] For the timer, the timer_setup function is called to set up the intermediate callback function. The first parameter of the intermediate callback function is of the timer_list type, and the callback function type defined by the abstraction layer is OsalTimerFunc. The timer_list and OsalTimerFunc can be embedded into a larger structure, and an instance of this structure is associated with the realTimer member of OsalTimer. In this way, in the intermediate callback function, the pointer to the larger structure can be found based on the formal parameter timer_list, and then the function pointer of the OsalTimerFunc type can be retrieved.
[0079] 3.4 Implement the abstraction layer callback deregistration function, call the kernel deregistration function when needed, and cancel the association between the intermediate callback function and the abstraction layer callback function.
[0080] Step S20: Provide a general data structure, implement its operation methods, and export them for use by the driver. This part corresponds to Figure 1 the function of the "general data structure" part.
[0081] In this embodiment, the driver program is a piece of code working in the kernel space and needs to undertake the following functions: communicate with the user space, communicate with the hardware, and communicate with the kernel. To communicate with the user space, the abstraction layer device driver subsystem needs to be used. To communicate with the hardware and the kernel, the basic capabilities of the operating system need to be used, and the basic capabilities of the operating system need to be encapsulated using the above-mentioned encapsulation methods of the abstraction layer structure, abstraction layer interface functions, and abstraction layer callback functions. The general data structure is a data structure provided by the kernel to accelerate the development of programs. This part has nothing to do with the kernel function but is a more underlying "toolkit", such as linked lists, queues, red-black trees, etc. The reason it is called a general data structure is that it can be applied not only to the kernel.
[0082] In driver development, it is inevitable to use some kernel data structures. These data structures are widely used and powerful, but may be affected by KABI (Kernel ABI, that is, the kernel application binary interface). Therefore, in order to achieve better compatibility and stability, a set of data structures used internally in the abstraction layer need to be implemented to replace the kernel data structures. Among them, the linked list is the most important part. In addition, for data communication between the user space and the kernel space, a general data buffer structure needs to be defined and a set of operation functions need to be provided. This set of functions should be able to implement the functions of writing basic data types to the buffer and reading basic data types from the buffer. These basic data types include integers, characters, strings, arrays, and floating-point types of different lengths. Through such a design, data interaction between the user space and the kernel space can be conveniently achieved.
[0083] Step S30: Implement an abstract layer device driver subsystem to provide a stable framework for writing driver programs.
[0084] The driver program is the link for user programs to operate hardware. Taking character devices as an example, in traditional driver development, an instance of the file_operations structure needs to be provided to the kernel. The user space initiates an IO operation through the virtual file system via a system call, and a corresponding function in the file_operations will be called, thus establishing a channel for information transfer between the user space and the kernel space. In this process, it is closely related to KABI. What is affected by KABI includes the driver registration function and the set of operation functions. For example, most operation functions use the file structure as the first parameter, and the file structure will be affected by the kernel configuration, resulting in changes in KABI, which in turn causes the driver binary file to be unable to run on kernel images with different configurations. Therefore, it is necessary to design an abstract layer device driver subsystem to shield the implementation details of the driver registration-related functions and file operation functions.
[0085] The abstract layer device driver subsystem is a new driver development framework, which is implemented based on the character device driver framework at the bottom layer. The driver registers with the abstract layer device driver framework in the form of a service. The application program establishes a connection with the driver service through the name of the service. As a client, the application program can send messages to the driver service. After receiving the message and performing corresponding processing, the driver needs to reply the processing result to the client. At the same time, the abstract layer device driver framework provides an event subscription mechanism. The client can subscribe to the events it cares about. When the driver detects the occurrence of a corresponding event, it will notify all clients that have subscribed to this event. When developing a driver using this framework, there is no need to use the registration and cancellation functions of character devices, nor to implement the set of operation functions in file_operations, thus avoiding the influence of KABI. The abstract layer device driver subsystem provides the following interfaces for driver development: register service, unregister service, send event, message dispatch callback (corresponding to Figure 1 "dispatch message" in ). The abstract layer device driver subsystem provides the following interfaces for application development: bind service (corresponding to the open system call), unbind service (corresponding to the close system call), send message (corresponding to the KOS_WRITE_READ command of ioctl), register event listener (corresponding to the KOS_LISTEN_EVENT_START commands of poll and ioctl), unregister event listener (corresponding to the KOS_LISTEN_EVENT_STOP of ioctl).
[0086] The implementation details of the abstract layer device driver subsystem are briefly described below.
[0087] Registration service interface: It receives two parameters, one is the service name and the other is a set of service message handling functions. Using the service name as the device name, it registers a character device and implements the open, release, ioctl, and poll operation functions. It also creates an IDeviceObject structure to record the information related to this service, including: service name, set of message handling functions, associated character device, and client list. Finally, it returns a pointer to the IDeviceObject structure.
[0088] Service unregistration interface: It receives one parameter, which is a pointer to the IDeviceObject structure. It retrieves the associated character device through the IDeviceObject structure, calls the character device unregistration function in the kernel to unregister this character device, and releases the memory occupied by the IDeviceObject structure and its members.
[0089] Message dispatching callback interface: This interface is a set of callback functions called by the device driver subsystem of the abstraction layer. When registering a service, the second parameter is a set of service message handling functions. This function set is saved using a structure named IDeviceIoService, and its members include: Open function, Dispatch function, and Release function. These functions are the interfaces for message dispatching and are implemented by specific device drivers.
[0090] When the character device corresponding to the service is opened, first, the open function in file_operations will be called. This open function is implemented in the abstract layer device driver subsystem, and its implementation logic is as follows: First, an IDeviceIoClient structure is created to represent a connected client. The member variables of this structure mainly include: a pointer to the IDeviceObject structure corresponding to the service, a list of events to be processed by the client, and private data. Then, the pointer to the IDeviceIoClient structure is saved in the client list in the IDeviceObject structure. Next, the IDeviceIoService structure is retrieved from the IDeviceObject structure, and the Open function of the IDeviceIoService structure is called. (Open is a function type that is initialized to a specific function in the driver. Using Open can call the Open function in the driver.) Accordingly, the Open function in the driver will be called, and the pointer to the IDeviceIoClient structure is passed as a parameter to the driver. The driver can call the basic operating system capabilities and platform peripheral drivers encapsulated by the abstract layer in the Open function to implement some logic for hardware initialization. Finally, the pointer to the IDeviceIoClient structure is saved in the private variable of the file structure associated with the character device.
[0091] When the character device corresponding to the service is closed, first, the release function in file_operations will be called. This release function is implemented in the abstract layer device driver subsystem, and its implementation logic is as follows: First, the private variable is retrieved from the file structure associated with the character device and converted into a pointer type of the IDeviceIoClient structure. Then, the pointer to the IDeviceObject structure is obtained through the members of the IDeviceIoClient structure. Next, the IDeviceIoService structure is retrieved from the IDeviceObject structure, and its member function Release is called. Accordingly, the Release function in the driver will be called, and the pointer to the IDeviceIoClient structure is passed as a parameter to the driver. The driver can call the basic operating system capabilities and platform peripherals encapsulated by the abstract layer in the Release function to implement logic such as hardware sleep and power-off.
[0092] The application sends messages to the driver by sending commands to the character device associated with the service through ioctl. The corresponding message-sending command is KOS_WRITE_READ. The device driver subsystem in the abstraction layer will process this command and dispatch the data carried by the command to the Dispatch function of the service in the form of a message. The specific logic is as follows: First, take out the private variable from the file structure associated with the character device, convert it into a pointer type of the IDeviceIoClient structure, then obtain the pointer of the IDeviceObject structure through the member of IDeviceIoClient, then take out IDeviceIoService from IDeviceObject and call its Dispatch function. The corresponding Dispatch function in the driver will be called, and the pointer of the IDeviceIoClient structure will be passed to the driver as a parameter. Copy the user-space parameters carried by ioctl to the kernel space and pass them to the driver as parameters of Dispatch. The driver can call the basic operating system capabilities and platform peripheral drivers encapsulated in the abstraction layer in the Dispatch function to implement the specific operation logic for the hardware. After the processing is completed, write the processing result to a buffer and then copy it to the user-space memory area pointed to by the ioctl parameter.
[0093] Event sending interface: It has three parameters. The first parameter is a pointer to the IDeviceObject structure, the second parameter is the event ID, and the third parameter is the content of the event. When the driver needs to actively send information to the application, such as a timer expiring or an interrupt arriving, it can notify the application through the event mechanism. For clients that need to receive events, they need to register an event listener with the service first, send the KOS_LISTEN_EVENT_START command through ioctl, and the corresponding IDeviceIoClient structure will record that this client is in the listening state. Then the application listens for whether there is data readable on the character device corresponding to the service through the poll system call. When the driver detects that an event has occurred, it will call the event sending interface to send the event. An event is a piece of data stored in memory. The first parameter of the event sending interface is of the IDeviceObject structure pointer type, and the parameter passed is the return value of registering the service. Through IDeviceObject, the list of connected clients can be obtained, and then the event ID (defined by the user to distinguish different events) and the event content are added to the event list of the clients in the listening state. Finally, wake up the user process in the poll state. At this time, the application can send the KOS_READ_DEV_EVENT command through ioctl to retrieve the event from the event list of the client IDeviceIoClient structure and copy it to the user space.
[0094] When the application opens the character device file node corresponding to the service through open, the Open function in the service will be called. When the application closes the character device file node corresponding to the service through close, the Release function in the service will be called. The call of the Open function indicates that a new client is connecting, and the call of the Release function indicates that a client is disconnecting. In addition to initializing and de-initializing the hardware, the Open and Release functions can also process the private data of the client. The private data of the client can be initialized in Open and de-initialized in the Release function. For example: A driver needs to manage several memory blocks. When client A connects, allocate memory block 1 for A to use. When client B connects, allocate memory block 2 for B to use. The address of the memory block can be used as the private data of the client and saved in the private member of IDeviceIoClient. When the client communicates with the service later, the pointer to the IDeviceIoClient structure corresponding to the client can be obtained from the first parameter of the Dispatch function, so as to retrieve the private data of the client and finally know which memory block needs to be processed.
[0095] In addition, an error can be returned in the Open of the driver to reject the client's connection request. For example, many hardware devices are exclusive. For example, a printer only allows one user at a time. Then when a second client initiates a connection, its connection needs to be rejected.
[0096] The driver service operations in the kernel space are carried by message handling functions. When the driver is initialized, a service entity is registered with the abstract layer device driver framework, and an instance of the message handling function is carried. When the user space sends a message to the service, the service in the kernel space will receive this message and perform corresponding service operations according to the content of the message. After the processing is completed, it will send the result to the client in the user space in the form of a reply message.
[0097] If there are interrupts or timer tasks in the driver, and it is necessary to send a message to the user space in the interrupt service function or task processing function, then the user space can be actively notified through the event reporting channel.
[0098] In addition, if different drivers in the kernel space need to access each other, in addition to directly making function calls, the corresponding services can also be found through the service management system, and the mutual access between driver functions can be achieved by sending messages to the services. This method provides a more flexible and decoupled inter-driver communication mechanism.
[0099] Step S40: Implement the platform peripheral driver set and provide it to other driver modules in the form of services or exported interfaces. This part corresponds to Figure 1 the function of the "platform driver" part in
[0100] The platform driver is equivalent to a development library and may be used by many other device drivers. For example, Driver A is a sensor that uses I2C communication, and Driver B is a sound card that also needs to use I2C communication. If both Driver A and Driver B implement the I2C communication function internally, a lot of duplicate code will be added. Therefore, it is necessary to implement this I2C driver in advance for all drivers to use. On the other hand, the platform driver can be regarded as a part of the abstraction layer. The driver can internally reference kernel structures and functions without worrying about being affected by KABI, as long as it ensures to expose a unified interface externally. If the platform driver is implemented outside the abstraction layer, all kernel structure and function calls need to be replaced with abstraction layer structures and functions, which is a huge workload equivalent to re-implementing the kernel function. Therefore, it is a wise move to encapsulate from a higher dimension.
[0101] These platform peripheral drivers cover various types such as GPIO, I2C, SPI, UART, ADC, DAC, RTC, DMA, etc. They are the on-chip peripheral drivers of the SOC and are usually used to provide services for other modules of the kernel and implement IO operations with peripheral modules.
[0102] To improve the code reusability, this part of the driver is pre-written. In this way, other modules can easily use these drivers by simply calling the corresponding interfaces. The encapsulation of the platform driver is based on the original kernel driver framework. Specifically, a service is registered for each driver. In the service, a set of operation function sets are defined according to different device types. These function sets will internally access the corresponding IO functions of the kernel. When writing other device driver programs, the corresponding operation functions can be obtained through the service interface. Then, by sending commands to the service, the IO operation of the underlying data can be achieved. Such a design makes the use of the driver more flexible and convenient.
[0103] Taking the most widely used GPIO driver as an example, illustrate the implementation principle of the platform driver. The GPIO platform peripheral driver needs to implement the following interface functions: GpioRead, GpioWrite, GpioGetDir, GpioSetDir, GpioSetIrq, GpioUnsetIrq, GpioEnableIrq, GpioDisableIrq.
[0104] GpioRead: Reads the GPIO level status. It takes one parameter, the GPIO number, and returns the level status of the current GPIO pin. 1 indicates high level and 0 indicates low level. Inside the function, it calls the gpio_get_value_cansleep kernel function to read the GPIO-related registers and obtain the level status of the GPIO input.
[0105] GpioWrite: Sets the GPIO level status. It takes two parameters. The first parameter is the GPIO number, and the second parameter is the level status. When the second parameter is 1, it means to output high level; when it is 0, it means to output low level. Inside the function, it calls the gpio_set_value_cansleep kernel function to set the GPIO-related registers and implement the output level control.
[0106] GpioGetDir: Gets the IO direction of the GPIO. It takes one parameter, the GPIO number, and returns the IO direction of the current GPIO pin. Returning 1 indicates the output mode, and returning 0 indicates the input mode. Inside the function, it calls the gpiod_get_direction kernel function to read the GPIO-related registers and obtain the IO direction.
[0107] GpioSetDir: Sets the IO direction of the GPIO. It takes two parameters. The first parameter is the GPIO number, and the second parameter is the IO direction. When the second parameter is set to 1, it means to set it to the output mode, and the kernel function gpio_direction_output is called to set the GPIO to the output mode; when the second parameter is set to 0, it means to set it to the input mode, and the kernel function gpio_direction_input is called to set the GPIO to the input mode.
[0108] GpioSetIrq: Register the GPIO interrupt, which takes four parameters. The first parameter is the GPIO number, the second parameter is the interrupt mode, the third parameter is a pointer to the interrupt handling function, and the fourth parameter will be passed to the interrupt handling function for processing. First, call the kernel function gpio_to_irq to obtain the corresponding interrupt number based on the GPIO number. Then, according to the interrupt number and the interrupt mode, call the kernel function request_irq to apply for an interrupt. The interrupt service function set during the application is just a transfer function named LinuxGpioIrq. The last parameter of the request_irq function is used to pass the parameters to the interrupt service program. Therefore, the third and fourth parameters of the GpioSetIrq function can be packed and passed to LinuxGpioIrq for processing through this parameter. Inside the LinuxGpioIrq function, first obtain the interrupt handling function set by GpioSetIrq and the parameters that need to be processed by the interrupt service program, then call the interrupt handling function and pass the parameters just obtained to handle the interrupt transaction.
[0109] GpioUnsetIrq: Deregister the GPIO interrupt, which takes one parameter, the GPIO number. First, call the kernel function gpio_to_irq to obtain the corresponding interrupt number based on the GPIO number, and then call the kernel function free_irq to deregister the interrupt according to the interrupt number.
[0110] GpioEnableIrq: Enable the GPIO interrupt, which takes one parameter, the GPIO number. First, call the kernel function gpio_to_irq to obtain the corresponding interrupt number based on the GPIO number, and then call the kernel function enable_irq to enable the corresponding interrupt according to the interrupt number.
[0111] GpioDisableIrq: Disable the GPIO interrupt, which takes one parameter, the GPIO number. First, call the kernel function gpio_to_irq to obtain the corresponding interrupt number based on the GPIO number, and then call the kernel function disable_irq_nosync to disable the corresponding interrupt according to the interrupt number.
[0112] Step S50: Use of the general user-space driver access library. The driver access library is part of the application program.
[0113] In a user-space program, library functions are used for service binding, and the library functions are called to send data and register event listeners. For example, the BindService function (with the service name as the parameter, which internally opens the character device corresponding to the service through the open system call and returns the file descriptor encapsulated as a service object) is called to bind to the required driver service. The SendData function (with the service object and message buffer as parameters. Internally, the ioctl system call is invoked with the KOS_WRITE_READ command, and the message buffer is used as a parameter to send the message to the service, and the Dispath function of the service will be called) is used to send data. The RegisterEventListener function (with the service object and event callback function as parameters. Internally, the ioctl system call is invoked with the KOS_LISTEN_EVENT_START command to notify the service to mark that this client needs to receive event notifications, and then a thread is started. In the thread, the poll system call is invoked to monitor the read status of the character device corresponding to the service. When the service actively reports an event, poll will be awakened from the waiting and sleeping state, and the event content is read from the service through the KOS_READ_DEV_EVENT ioctl command, and then the registered event callback function is called to handle the event) is called to register an event listener. In this way, when the user-space program acts as a client, it can easily send data to the server and register a listener to monitor the events reported by the server. This encapsulation measure greatly simplifies the steps of writing an application program.
[0114] Based on the above steps S10 - S50, an operating system abstraction layer is built between the kernel and the driver program, and the kernel capabilities required by the driver are encapsulated into stable KABI (Application Binary Interface) interfaces. The driver program is written by calling the interfaces provided by this abstraction layer instead of directly calling the system's KAPI functions. When the kernel is upgraded, since the KABI of the abstraction layer remains stable, the driver module compiled on the old version of the kernel can directly run on the new version of the system, thus ensuring the compatibility and stability of the driver program. It effectively solves the compatibility problem of the driver program that may be caused by kernel upgrades.
[0115] It should be noted that although the above embodiments describe the various steps in a specific order, those skilled in the art can understand that for the purpose of achieving the effects of the present invention, it is not necessary for different steps to be executed in such an order. They can be executed simultaneously (in parallel) or in other orders, and these variations are all within the protection scope of the present invention.
[0116] So far, the technical solution of the present invention has been described in conjunction with the preferred embodiments shown in the accompanying drawings. However, it is easily understood by those skilled in the art that the protection scope of the present invention is obviously not limited to these specific embodiments. Without departing from the principle of the present invention, those skilled in the art can make equivalent changes or substitutions to the relevant technical features, and the technical solutions after these changes or substitutions will fall within the protection scope of the present invention.
Claims
1. A method for implementing an operating system abstraction layer for driver development, characterized in that: The following steps are involved: According to different basic capabilities, define corresponding abstract layer structures and abstract layer interface functions, or define corresponding abstract layer structures, abstract layer interface functions and abstract layer callback functions, encapsulate the basic capabilities of the operating system, provide stable interfaces and export them to the driver for use. The basic capabilities of the operating system include thread use, process synchronization, memory use, interrupt use, IO mapping, file access, and timer use; Provide a common data structure, implement its operation methods, and export it to the driver for use; Implement an abstract layer device driver subsystem to provide a stable framework for writing drivers; Implement a platform peripheral driver set and provide it to other driver modules in the form of services or exported interfaces; Implement a general user space driver access library to communicate with the driver through library functions; The steps of defining the abstract layer structure include: Based on the basic capabilities, define an abstract layer structure named "OsalXXX", where Osal is a specified prefix and XXX represents the current basic capabilities; In the abstract layer structure, define a pointer member variable realXXX of empty type, which is used to associate with a specific kernel structure instance in subsequent operations, where real is a specified prefix; Implement an abstract layer structure initialization function to associate the kernel structure with the abstract layer structure, and save the kernel structure variable as a pointer member variable of the abstract layer structure; Implement an abstract layer structure operation function. The first parameter of the function is the pointer variable of the abstract layer structure, and the following parameters correspond to the parameters of the specific kernel function. Implement an abstract layer structure destruction function to deinitialize the associated kernel structure and release memory.
2. The method for implementing an operating system abstraction layer according to claim 1, characterized in that: The implementation of an abstract layer structure initialization function for associating the kernel structure with the abstract layer structure and saving the kernel structure variable as a pointer member variable of the abstract layer structure also includes: When the basic capability involves multiple structure types or requires auxiliary variables, the related structure types and auxiliary variables are integrated into an intermediate structure, and then the instance of the intermediate structure type is saved in the pointer member variable of the abstract layer structure.
3. The method for implementing an operating system abstraction layer according to claim 1, characterized in that: The steps of defining the abstract layer interface function include: Use a unified naming convention to define abstract layer interface functions. The interface function name starts with Osal, and the function parameters are basic data types or abstract layer structure types. The function body implementation includes the unique logic of the abstract layer and calling the kernel function to implement specific operations; Export kernel symbols using EXPORT_SYMBOL.
4. The method for implementing an operating system abstraction layer according to claim 1, characterized in that: The steps of defining the abstract layer callback function include: Define an abstract layer callback function pointer type, which is used to drive the callback function registered with the abstract layer; Implement a transfer callback function, which is triggered when the system calls, and calls the corresponding abstract layer callback function inside the function; Implement the abstract layer callback registration function to register the transfer callback function into the system and bind the transfer callback function with the abstract layer callback function; Implement the abstract layer callback deregistration function to call the kernel's deregistration function and cancel the association between the transfer callback function and the abstract layer callback function.
5. The method for implementing an operating system abstraction layer according to claim 1, characterized in that: The general data structure includes a linked list, a queue, a red-black tree and a general data buffer structure used for data communication between the user space and the kernel space, and realizes the function of writing and reading basic data types to the buffer by providing a group of operation functions.
6. The method for implementing an operating system abstraction layer according to claim 1, characterized in that: The abstract layer device driver subsystem provides registration service, deregistration service, event sending and message dispatching callback interface for driver development, and the abstract layer device driver subsystem provides binding service, unbinding service, message sending, event listener registration and event listener deregistration interface for application development; The driver is registered with the abstract layer device driver framework in the form of a service. The application establishes a connection with the driver development through the service name and sends a message to the driver service as a client. The driver processes the message and returns the result to the application.
7. The method for implementing an operating system abstraction layer according to claim 1, characterized in that: The platform peripheral driver set includes GPIO, I2C, SPI, UART, ADC, DAC, RTC and DMA peripheral drivers. By registering a service for each driver, a set of operation function sets are defined according to different device types. The function set internally accesses the corresponding IO function of the kernel. Other device drivers obtain the corresponding operation function through the service interface and implement the IO operation of the underlying data by sending messages to the service.
8. The method for implementing an operating system abstraction layer according to claim 1, characterized in that: The general user space driver access library encapsulates the service binding, data sending and event monitoring functions into one library file, and the user space program uses the library function to send data to the server and register the listener.
Citation Information
Patent Citations
Hardware equipment driving system based on microkernel and driving method thereof
CN113268275A
Method and system for separating product development kit from operating system and hardware
CN117519783A