Android provides a user-level lightweight LOG Mechanism. Its implementation runs through Java, JNI, local c/c ++ implementation, Linux kernel driver, and Other Android layers, and is simple and clear enough, it is a pretty good case. This series of articles explains the internal implementation mechanism of the LOG mechanism. This article is part 4 of the series. It explains the implementation of the device-driven Logger in the Linux kernel. Logger is a MISC Driver written by Android for Linux. It uses cyclic queues to implement readers/writers. Logger is the core of the implementation of the entire LOG mechanism.
Keywords: LINUX driver, Android, Reader/writer, MISC driver, cyclic queue, waiting queue, blocking I/O
The Log driver is implemented in kernel/drivers/staging/android/Logger. c.
I. Initialization
Let's take a look at a LINUX driver and first look at how it is initialized.
Static int _ init init_log (struct logger_log * log)
{
Int ret;
Ret = misc_register (& log-> misc );
If (unlikely (ret )){
Printk (KERN_ERR "logger: failed to register misc"
"Device for log '% s '! \ N ", log-> misc. name );
Return ret;
}
Printk (KERN_INFO "logger: created % luK log '% s' \ n ",
(Unsigned long) log-> size> 10, log-> misc. name );
Return 0;
}
Static int _ init logger_init (void)
{
Int ret;
Ret = init_log (& log_main );
If (unlikely (ret ))
Goto out;
Ret = init_log (& log_events );
If (unlikely (ret ))
Goto out;
Ret = init_log (& log_radio );
If (unlikely (ret ))
Goto out;
Ret = init_log (& log_system );
If (unlikely (ret ))
Goto out;
Out:
Return ret;
}
Device_initcall (logger_init );
The entry point of the entire Logger driver is Logger_init (). It uses init_log (struct logger_log * log) to initialize four logger_log types: log_main, log_events, log_radio, and log_system, the four structural variables respectively record the four storage bodies of log. Logger implements four drivers for the same device from these four variables, while the log driver is of the MISC type and is registered with the system through misc_register. After four registrations, the corresponding MINOR IDs are different. Looger also uses minor to identify which driver is used.
Static struct logger_log * get_log_from_minor (int minor)
{
If (log_main.misc.minor = minor)
Return & log_main;
If (log_events.misc.minor = minor)
Return & log_events;
If (log_radio.misc.minor = minor)
Return & log_radio;
If (log_system.misc.minor = minor)
Return & log_system;
Return NULL;
}
This article describes how to implement the Logger driver using log_main.
II. Key Data Structure
The struct variable log_main is mentioned in the previous section. Now let's look at its definition.
Log_main stores the variables required for the Logger operation. Buffer points to a static array, which is used to store read and write data. Logger uses it to form a logical cyclic queue. The writer can write something to the place where w_off points, once there is content, the readers in the wq queue will be notified to read the content. Because buffer implements a cyclic queue, the buffer size and size are often used for high-order Division operations. It must be a two-power number. Mutex is used to protect the key resource log_main. Logger is a drive of the MISC type. It retains the misc variable of the miscdevice type. Misc also has the most critical file_operations structure, which is the entry for applications to interact with drivers through file operations.
Iii. functions implemented by Logger
From the type definition of log_main above, we can see what Logger implements. In a word, Logger implements the reader and synchronization. However, the Logger reader has some special characteristics. The writer's write operations will not be blocked or full overflow, that is, as long as there is content to be written, if the Buffer is exceeded, it will overwrite the old one [combined with the specific write operations of the application]. The reader will be blocked and suspended because the content to be read is empty, all suspended readers will be awakened [combined with specific application read operations].
Next we will look at the specific implementation from the perspective of readers and writers.
3.1. Writer implementation
Take a look at the key structure logger_fops: file_operations in the figure in Section 2. The writer's key implementation is based on the implementation of open, release, and write functions, which are assigned to logger_open () respectively () /logger_release ()/logger_aio_write ().
Logger_open () is used by the writer to obtain the instance of logger_log through minor id, and assign the value to the private_data file passed in the function parameter.
Logger_release () does not need to do anything for the writer.
Logger_poll () does not need to be blocked because of write. So it is detected that the file is not opened because of reading (! (File-> f_mode & FMODE_READ), POLLOUT | POLLWRNORM is returned directly. Can be written in any way.
Logger_aio_write () is the key to writing data (that is, log information. The asynchronous IO method is used here. This method can be called when an application uses write ()/writev () and aio_write.
When logging log information, the interface used for writing logs is writev () and data in the vec format is written. The data written here is of course vec data. In addition, data of the logger_entry type is also written to record time and other information. When writing data to a specific buffer, because the storage location may not be consecutive, but it is written at the end and start of the buffer, it is necessary to make a judgment and there may be two buffer writes. The data in the parameter comes from the user space and cannot be used directly in the kernel space. You must use copy_from_user (). After writing the code, use wake_up_interruptible (& log-> wq) to wake up all the readers who are waiting.
3.2. Reader implementation
Take a look at the key structure logger_fops: file_operations in the figure in Section 2. The writer's key implementation is based on the implementation of open, release, and read functions, which are assigned to logger_open () respectively () /logger_release ()/logger_read ().
Logger_open () is used to obtain the logger_log instance through minor id, dynamically apply for a logger_reader type reader, and add it to the end of the logger_log reader list readers, then assign the value to the private_data file passed in the function parameter.
Logger_release () corresponds to logger_open (). This reader is removed from the logger_log.readers list and released the dynamically applied instance.
Logger_poll () is called poll ()/select () to check whether the application can be written. So here we will use poll_wait () to add the poll_table parameter to logger_log.wq. If there is any readable content, we will set the readable mark | = POLLIN | POLLRDNORM.
Logger_read () is the key to reading data (that is, log information.
Before reading data, ensure that there is data. Otherwise, the reader will be suspended on the wq queue of logger_log. When data is read from a specific buffer, the storage location may not be consecutive and is stored at the end and start of the buffer. Therefore, you need to determine whether to read data from the buffer twice. Data comes from the kernel space. To pass the data through the user space parameters, copy_to_user () is required ().
3.3 Implementation of cyclic queue
This is the most classic case in the data structure. I will not explain how to implement it here. I just want to list important structures. I just hope that readers will still remember the logical and physical structures in the data structure.
Queue size: log_main.size
Write header: log_main.w_off
Read header: logger_reader.r_off
Empty queue: log_main.w_off = logger_reader.r_off
Full queue judgment: No
3.4 ioctl implementation
Logger provides the function for applications to obtain information or control LOGbuffer through ioctl. Logger registers logger_ioctl with file_operations to the file system to implement this function. Logger_ioctl () provides the following ioctl control commands: LOGGER_GET_LOG_BUF_SIZE/LOGGER_GET_LOG_LEN/LOGGER_GET_NEXT_ENTRY_LEN/LOGGER_FLUSH_LOG. Implementation is simple:
LOGGER_GET_LOG_BUF_SIZE obtains the Buffer size and returns logger_log.size directly;
LOGGER_GET_LOG_LEN is only valid for reading and obtains the current LOG size. If the storage is continuous, log-> w_off-reader-> r_off; otherwise, log-> size-reader-> r_off) + log-> w_off;
LOGGER_GET_NEXT_ENTRY_LEN obtains the length of the Entry, which is only valid for reading.
LOGGER_FLUSH_LOG is only valid for writing. The so-called flush log directly resets the r_off of each reader and sets the head to be accessed by the new reader.
3.5 blocking I/O and asynchronous I/O
In addition.