Android日志系统驱动程序Logger源代码分析

本文涉及的产品
日志服务 SLS,月写入数据量 50GB 1个月
简介:   我们知道,在Android系统中,提供了一个轻量级的日志系统,这个日志系统是以驱动程序的形式实现在内核空间的,而在用户空间分别提供了Java接口和C/C++接口来使用这个日志系统,取决于你编写的是Android应用程序还是系统组件。

  我们知道,在Android系统中,提供了一个轻量级的日志系统,这个日志系统是以驱动程序的形式实现在内核空间的,而在用户空间分别提供了Java接口和C/C++接口来使用这个日志系统,取决于你编写的是Android应用程序还是系统组件。在前面的文章浅谈Android系统开发中LOG的使用中,已经简要地介绍了在Android应用程序开发中Log的使用方法,在这一篇文章中,我们将更进一步地分析Logger驱动程序的源代码,使得我们对Android日志系统有一个深刻的认识。

        既然Android 日志系统是以驱动程序的形式实现在内核空间的,我们就需要获取Android内核源代码来分析了,请参照前面在Ubuntu上下载、编译和安装Android最新源代码在Ubuntu上下载、编译和安装Android最新内核源代码(Linux Kernel)两篇文章,下载好Android源代码工程。Logger驱动程序主要由两个文件构成,分别是:

       kernel/common/drivers/staging/android/logger.h

       kernel/common/drivers/staging/android/logger.c

       接下来,我们将分别介绍Logger驱动程序的相关数据结构,然后对Logger驱动程序源代码进行情景分析,分别日志系统初始化情景、日志读取情景和日志写入情景。

       一. Logger驱动程序的相关数据结构。

      我们首先来看logger.h头文件的内容:

[cpp]  view plain copy
  1. #ifndef _LINUX_LOGGER_H  
  2. #define _LINUX_LOGGER_H  
  3.   
  4. #include <linux/types.h>  
  5. #include <linux/ioctl.h>  
  6.   
  7. struct logger_entry {  
  8.     __u16       len;    /* length of the payload */  
  9.     __u16       __pad;  /* no matter what, we get 2 bytes of padding */  
  10.     __s32       pid;    /* generating process's pid */  
  11.     __s32       tid;    /* generating process's tid */  
  12.     __s32       sec;    /* seconds since Epoch */  
  13.     __s32       nsec;   /* nanoseconds */  
  14.     char        msg[0]; /* the entry's payload */  
  15. };  
  16.   
  17. #define LOGGER_LOG_RADIO    "log_radio" /* radio-related messages */  
  18. #define LOGGER_LOG_EVENTS   "log_events"    /* system/hardware events */  
  19. #define LOGGER_LOG_MAIN     "log_main"  /* everything else */  
  20.   
  21. #define LOGGER_ENTRY_MAX_LEN        (4*1024)  
  22. #define LOGGER_ENTRY_MAX_PAYLOAD    \  
  23.     (LOGGER_ENTRY_MAX_LEN - sizeof(struct logger_entry))  
  24.   
  25. #define __LOGGERIO  0xAE  
  26.   
  27. #define LOGGER_GET_LOG_BUF_SIZE     _IO(__LOGGERIO, 1) /* size of log */  
  28. #define LOGGER_GET_LOG_LEN      _IO(__LOGGERIO, 2) /* used log len */  
  29. #define LOGGER_GET_NEXT_ENTRY_LEN   _IO(__LOGGERIO, 3) /* next entry len */  
  30. #define LOGGER_FLUSH_LOG        _IO(__LOGGERIO, 4) /* flush log */  
  31.   
  32. #endif /* _LINUX_LOGGER_H */  
        struct logger_entry是一个用于描述一条Log记录的结构体。len成员变量记录了这条记录的有效负载的长度,有效负载指定的日志记录本身的长度,但是不包括用于描述这个记录的struct logger_entry结构体。回忆一下我们调用android.util.Log接口来使用日志系统时,会指定日志的优先级别Priority、Tag字符串以及Msg字符串,Priority + Tag + Msg三者内容的长度加起来就是记录的有效负载长度了。__pad成员变量是用来对齐结构体的。pid和tid成员变量分别用来记录是哪条进程写入了这条记录。sec和nsec成员变量记录日志写的时间。msg成员变量记录的就有效负载的内容了,它的大小由len成员变量来确定。

       接着定义两个宏:

       #define LOGGER_ENTRY_MAX_LEN             (4*1024)

       #define LOGGER_ENTRY_MAX_PAYLOAD   \

                         (LOGGER_ENTRY_MAX_LEN - sizeof(struct logger_entry))

      从这两个宏可以看出,每条日志记录的有效负载长度加上结构体logger_entry的长度不能超过4K个字节。

      logger.h文件中还定义了其它宏,读者可以自己分析,在下面的分析中,碰到时,我们也会详细解释。

      再来看logger.c文件中,其它相关数据结构的定义:

[cpp]  view plain copy
  1. /* 
  2.  * struct logger_log - represents a specific log, such as 'main' or 'radio' 
  3.  * 
  4.  * This structure lives from module insertion until module removal, so it does 
  5.  * not need additional reference counting. The structure is protected by the 
  6.  * mutex 'mutex'. 
  7.  */  
  8. struct logger_log {  
  9.     unsigned char *     buffer; /* the ring buffer itself */  
  10.     struct miscdevice   misc;   /* misc device representing the log */  
  11.     wait_queue_head_t   wq; /* wait queue for readers */  
  12.     struct list_head    readers; /* this log's readers */  
  13.     struct mutex        mutex;  /* mutex protecting buffer */  
  14.     size_t          w_off;  /* current write head offset */  
  15.     size_t          head;   /* new readers start here */  
  16.     size_t          size;   /* size of the log */  
  17. };  
  18.   
  19. /* 
  20.  * struct logger_reader - a logging device open for reading 
  21.  * 
  22.  * This object lives from open to release, so we don't need additional 
  23.  * reference counting. The structure is protected by log->mutex. 
  24.  */  
  25. struct logger_reader {  
  26.     struct logger_log * log;    /* associated log */  
  27.     struct list_head    list;   /* entry in logger_log's list */  
  28.     size_t          r_off;  /* current read head offset */  
  29. };  
  30.   
  31. /* logger_offset - returns index 'n' into the log via (optimized) modulus */  
  32. #define logger_offset(n)    ((n) & (log->size - 1))  
        结构体struct logger_log就是真正用来保存日志的地方了。buffer成员变量变是用保存日志信息的内存缓冲区,它的大小由size成员变量确定。从misc成员变量可以看出,logger驱动程序使用的设备属于misc类型的设备,通过在Android模拟器上执行cat /proc/devices命令(可参考 在Ubuntu上下载、编译和安装Android最新内核源代码(Linux Kernel) 一文),可以看出,misc类型设备的主设备号是10。关于主设备号的相关知识,可以参考 Android学习启动篇 一文中提到的Linux Driver Development一书。wq成员变量是一个等待队列,用于保存正在等待读取日志的进程。readers成员变量用来保存当前正在读取日志的进程,正在读取日志的进程由结构体logger_reader来描述。mutex成员变量是一个互斥量,用来保护log的并发访问。可以看出,这里的日志系统的读写问题,其实是一个生产者-消费者的问题,因此,需要互斥量来保护log的并发访问。 w_off成员变量用来记录下一条日志应该从哪里开始写。head成员变量用来表示打开日志文件中,应该从哪一个位置开始读取日志。

       结构体struct logger_reader用来表示一个读取日志的进程,log成员变量指向要读取的日志缓冲区。list成员变量用来连接其它读者进程。r_off成员变量表示当前要读取的日志在缓冲区中的位置。

       struct logger_log结构体中用于保存日志信息的内存缓冲区buffer是一个循环使用的环形缓冲区,缓冲区中保存的内容是以struct logger_entry为单位的,每个单位的组成为:

       struct logger_entry | priority | tag | msg

       由于是内存缓冲区buffer是一个循环使用的环形缓冲区,给定一个偏移值,它在buffer中的位置由下logger_offset来确定:

       #define logger_offset(n)          ((n) & (log->size - 1))

       二. Logger驱动程序模块的初始化过程分析。

       继续看logger.c文件,定义了三个日志设备:

[cpp]  view plain copy
  1. /* 
  2.  * Defines a log structure with name 'NAME' and a size of 'SIZE' bytes, which 
  3.  * must be a power of two, greater than LOGGER_ENTRY_MAX_LEN, and less than 
  4.  * LONG_MAX minus LOGGER_ENTRY_MAX_LEN. 
  5.  */  
  6. #define DEFINE_LOGGER_DEVICE(VAR, NAME, SIZE) \  
  7. static unsigned char _buf_ ## VAR[SIZE]; \  
  8. static struct logger_log VAR = { \  
  9.     .buffer = _buf_ ## VAR, \  
  10.     .misc = { \  
  11.         .minor = MISC_DYNAMIC_MINOR, \  
  12.         .name = NAME, \  
  13.         .fops = &logger_fops, \  
  14.         .parent = NULL, \  
  15.     }, \  
  16.     .wq = __WAIT_QUEUE_HEAD_INITIALIZER(VAR .wq), \  
  17.     .readers = LIST_HEAD_INIT(VAR .readers), \  
  18.     .mutex = __MUTEX_INITIALIZER(VAR .mutex), \  
  19.     .w_off = 0, \  
  20.     .head = 0, \  
  21.     .size = SIZE, \  
  22. };  
  23.   
  24. DEFINE_LOGGER_DEVICE(log_main, LOGGER_LOG_MAIN, 64*1024)  
  25. DEFINE_LOGGER_DEVICE(log_events, LOGGER_LOG_EVENTS, 256*1024)  
  26. DEFINE_LOGGER_DEVICE(log_radio, LOGGER_LOG_RADIO, 64*1024)  
       分别是log_main、log_events和log_radio,名称分别LOGGER_LOG_MAIN、LOGGER_LOG_EVENTS和LOGGER_LOG_RADIO,它们的次设备号为MISC_DYNAMIC_MINOR,即为在注册时动态分配。在logger.h文件中,有这三个宏的定义:

       #define LOGGER_LOG_RADIO "log_radio" /* radio-related messages */
       #define LOGGER_LOG_EVENTS "log_events" /* system/hardware events */
       #define LOGGER_LOG_MAIN "log_main" /* everything else */

       注释说明了这三个日志设备的用途。注册的日志设备文件操作方法为logger_fops:

[cpp]  view plain copy
  1. static struct file_operations logger_fops = {  
  2.     .owner = THIS_MODULE,  
  3.     .read = logger_read,  
  4.     .aio_write = logger_aio_write,  
  5.     .poll = logger_poll,  
  6.     .unlocked_ioctl = logger_ioctl,  
  7.     .compat_ioctl = logger_ioctl,  
  8.     .open = logger_open,  
  9.     .release = logger_release,  
  10. };  

       日志驱动程序模块的初始化函数为logger_init:

[cpp]  view plain copy
  1. static int __init logger_init(void)  
  2. {  
  3.     int ret;  
  4.   
  5.     ret = init_log(&log_main);  
  6.     if (unlikely(ret))  
  7.         goto out;  
  8.   
  9.     ret = init_log(&log_events);  
  10.     if (unlikely(ret))  
  11.         goto out;  
  12.   
  13.     ret = init_log(&log_radio);  
  14.     if (unlikely(ret))  
  15.         goto out;  
  16.   
  17. out:  
  18.     return ret;  
  19. }  
  20. device_initcall(logger_init);  
        logger_init函数通过调用init_log函数来初始化了上述提到的三个日志设备:

[cpp]  view plain copy
  1. static int __init init_log(struct logger_log *log)  
  2. {  
  3.     int ret;  
  4.   
  5.     ret = misc_register(&log->misc);  
  6.     if (unlikely(ret)) {  
  7.         printk(KERN_ERR "logger: failed to register misc "  
  8.                "device for log '%s'!\n", log->misc.name);  
  9.         return ret;  
  10.     }  
  11.   
  12.     printk(KERN_INFO "logger: created %luK log '%s'\n",  
  13.            (unsigned long) log->size >> 10, log->misc.name);  
  14.   
  15.     return 0;  
  16. }  
        init_log函数主要调用了misc_register函数来注册misc设备,misc_register函数定义在kernel/common/drivers/char/misc.c文件中:

[cpp]  view plain copy
  1. /** 
  2.  *      misc_register   -       register a miscellaneous device 
  3.  *      @misc: device structure 
  4.  * 
  5.  *      Register a miscellaneous device with the kernel. If the minor 
  6.  *      number is set to %MISC_DYNAMIC_MINOR a minor number is assigned 
  7.  *      and placed in the minor field of the structure. For other cases 
  8.  *      the minor number requested is used. 
  9.  * 
  10.  *      The structure passed is linked into the kernel and may not be 
  11.  *      destroyed until it has been unregistered. 
  12.  * 
  13.  *      A zero is returned on success and a negative errno code for 
  14.  *      failure. 
  15.  */  
  16.   
  17. int misc_register(struct miscdevice * misc)  
  18. {  
  19.         struct miscdevice *c;  
  20.         dev_t dev;  
  21.         int err = 0;  
  22.   
  23.         INIT_LIST_HEAD(&misc->list);  
  24.   
  25.         mutex_lock(&misc_mtx);  
  26.         list_for_each_entry(c, &misc_list, list) {  
  27.                 if (c->minor == misc->minor) {  
  28.                         mutex_unlock(&misc_mtx);  
  29.                         return -EBUSY;  
  30.                 }  
  31.         }  
  32.   
  33.         if (misc->minor == MISC_DYNAMIC_MINOR) {  
  34.                 int i = DYNAMIC_MINORS;  
  35.                 while (--i >= 0)  
  36.                         if ( (misc_minors[i>>3] & (1 << (i&7))) == 0)  
  37.                                 break;  
  38.                 if (i<0) {  
  39.                         mutex_unlock(&misc_mtx);  
  40.                         return -EBUSY;  
  41.                 }  
  42.                 misc->minor = i;  
  43.         }  
  44.   
  45.         if (misc->minor < DYNAMIC_MINORS)  
  46.                 misc_minors[misc->minor >> 3] |= 1 << (misc->minor & 7);  
  47.         dev = MKDEV(MISC_MAJOR, misc->minor);  
  48.   
  49.         misc->this_device = device_create(misc_class, misc->parent, dev, NULL,  
  50.                                           "%s", misc->name);  
  51.         if (IS_ERR(misc->this_device)) {  
  52.                 err = PTR_ERR(misc->this_device);  
  53.                 goto out;  
  54.         }  
  55.   
  56.         /* 
  57.          * Add it to the front, so that later devices can "override" 
  58.          * earlier defaults 
  59.          */  
  60.         list_add(&misc->list, &misc_list);  
  61.  out:  
  62.         mutex_unlock(&misc_mtx);  
  63.         return err;  
  64. }  
        注册完成后,通过device_create创建设备文件节点。这里,将创建/dev/log/main、/dev/log/events和/dev/log/radio三个设备文件,这样,用户空间就可以通过读写这三个文件和驱动程序进行交互。

        三. Logger驱动程序的日志记录读取过程分析。

        继续看logger.c 文件,注册的读取日志设备文件的方法为logger_read:

[cpp]  view plain copy
  1. /* 
  2.  * logger_read - our log's read() method 
  3.  * 
  4.  * Behavior: 
  5.  * 
  6.  *  - O_NONBLOCK works 
  7.  *  - If there are no log entries to read, blocks until log is written to 
  8.  *  - Atomically reads exactly one log entry 
  9.  * 
  10.  * Optimal read size is LOGGER_ENTRY_MAX_LEN. Will set errno to EINVAL if read 
  11.  * buffer is insufficient to hold next entry. 
  12.  */  
  13. static ssize_t logger_read(struct file *file, char __user *buf,  
  14.                size_t count, loff_t *pos)  
  15. {  
  16.     struct logger_reader *reader = file->private_data;  
  17.     struct logger_log *log = reader->log;  
  18.     ssize_t ret;  
  19.     DEFINE_WAIT(wait);  
  20.   
  21. start:  
  22.     while (1) {  
  23.         prepare_to_wait(&log->wq, &wait, TASK_INTERRUPTIBLE);  
  24.   
  25.         mutex_lock(&log->mutex);  
  26.         ret = (log->w_off == reader->r_off);  
  27.         mutex_unlock(&log->mutex);  
  28.         if (!ret)  
  29.             break;  
  30.   
  31.         if (file->f_flags & O_NONBLOCK) {  
  32.             ret = -EAGAIN;  
  33.             break;  
  34.         }  
  35.   
  36.         if (signal_pending(current)) {  
  37.             ret = -EINTR;  
  38.             break;  
  39.         }  
  40.   
  41.         schedule();  
  42.     }  
  43.   
  44.     finish_wait(&log->wq, &wait);  
  45.     if (ret)  
  46.         return ret;  
  47.   
  48.     mutex_lock(&log->mutex);  
  49.   
  50.     /* is there still something to read or did we race? */  
  51.     if (unlikely(log->w_off == reader->r_off)) {  
  52.         mutex_unlock(&log->mutex);  
  53.         goto start;  
  54.     }  
  55.   
  56.     /* get the size of the next entry */  
  57.     ret = get_entry_len(log, reader->r_off);  
  58.     if (count < ret) {  
  59.         ret = -EINVAL;  
  60.         goto out;  
  61.     }  
  62.   
  63.     /* get exactly one entry from the log */  
  64.     ret = do_read_log_to_user(log, reader, buf, ret);  
  65.   
  66. out:  
  67.     mutex_unlock(&log->mutex);  
  68.   
  69.     return ret;  
  70. }  
       注意,在函数开始的地方,表示读取日志上下文的struct logger_reader是保存在文件指针的private_data成员变量里面的,这是在打开设备文件时设置的,设备文件打开方法为logger_open:

[cpp]  view plain copy
  1. /* 
  2.  * logger_open - the log's open() file operation 
  3.  * 
  4.  * Note how near a no-op this is in the write-only case. Keep it that way! 
  5.  */  
  6. static int logger_open(struct inode *inode, struct file *file)  
  7. {  
  8.     struct logger_log *log;  
  9.     int ret;  
  10.   
  11.     ret = nonseekable_open(inode, file);  
  12.     if (ret)  
  13.         return ret;  
  14.   
  15.     log = get_log_from_minor(MINOR(inode->i_rdev));  
  16.     if (!log)  
  17.         return -ENODEV;  
  18.   
  19.     if (file->f_mode & FMODE_READ) {  
  20.         struct logger_reader *reader;  
  21.   
  22.         reader = kmalloc(sizeof(struct logger_reader), GFP_KERNEL);  
  23.         if (!reader)  
  24.             return -ENOMEM;  
  25.   
  26.         reader->log = log;  
  27.         INIT_LIST_HEAD(&reader->list);  
  28.   
  29.         mutex_lock(&log->mutex);  
  30.         reader->r_off = log->head;  
  31.         list_add_tail(&reader->list, &log->readers);  
  32.         mutex_unlock(&log->mutex);  
  33.   
  34.         file->private_data = reader;  
  35.     } else  
  36.         file->private_data = log;  
  37.   
  38.     return 0;  
  39. }  
       新打开日志设备文件时,是从log->head位置开始读取日志的,保存在struct logger_reader的成员变量r_off中。

       start标号处的while循环是在等待日志可读,如果已经没有新的日志可读了,那么就要读进程就要进入休眠状态,等待新的日志写入后再唤醒,这是通过prepare_wait和schedule两个调用来实现的。如果没有新的日志可读,并且设备文件不是以非阻塞O_NONBLOCK的方式打开或者这时有信号要处理(signal_pending(current)),那么就直接返回,不再等待新的日志写入。判断当前是否有新的日志可读的方法是:

       ret = (log->w_off == reader->r_off);

       即判断当前缓冲区的写入位置和当前读进程的读取位置是否相等,如果不相等,则说明有新的日志可读。

       继续向下看,如果有新的日志可读,那么就,首先通过get_entry_len来获取下一条可读的日志记录的长度,从这里可以看出,日志读取进程是以日志记录为单位进行读取的,一次只读取一条记录。get_entry_len的函数实现如下:

[cpp]  view plain copy
  1. /* 
  2.  * get_entry_len - Grabs the length of the payload of the next entry starting 
  3.  * from 'off'. 
  4.  * 
  5.  * Caller needs to hold log->mutex. 
  6.  */  
  7. static __u32 get_entry_len(struct logger_log *log, size_t off)  
  8. {  
  9.     __u16 val;  
  10.   
  11.     switch (log->size - off) {  
  12.     case 1:  
  13.         memcpy(&val, log->buffer + off, 1);  
  14.         memcpy(((char *) &val) + 1, log->buffer, 1);  
  15.         break;  
  16.     default:  
  17.         memcpy(&val, log->buffer + off, 2);  
  18.     }  
  19.   
  20.     return sizeof(struct logger_entry) + val;  
  21. }  
        上面我们提到,每一条日志记录是由两大部分组成的,一个用于描述这条日志记录的结构体struct logger_entry,另一个是记录体本身,即有效负载。结构体struct logger_entry的长度是固定的,只要知道有效负载的长度,就可以知道整条日志记录的长度了。而有效负载的长度是记录在结构体struct logger_entry的成员变量len中,而len成员变量的地址与struct logger_entry的地址相同,因此,只需要读取记录的开始位置的两个字节就可以了。又由于日志记录缓冲区是循环使用的,这两个节字有可能是第一个字节存放在缓冲区最后一个字节,而第二个字节存放在缓冲区的第一个节,除此之外,这两个字节都是连在一起的。因此,分两种情况来考虑,对于前者,分别通过读取缓冲区最后一个字节和第一个字节来得到日志记录的有效负载长度到本地变量val中,对于后者,直接读取连续两个字节的值到本地变量val中。这两种情况是通过判断日志缓冲区的大小和要读取的日志记录在缓冲区中的位置的差值来区别的,如果相差1,就说明是前一种情况了。最后,把有效负载的长度val加上struct logger_entry的长度就得到了要读取的日志记录的总长度了。

       接着往下看,得到了要读取的记录的长度,就调用do_read_log_to_user函数来执行真正的读取动作:

[cpp]  view plain copy
  1. static ssize_t do_read_log_to_user(struct logger_log *log,  
  2.                    struct logger_reader *reader,  
  3.                    char __user *buf,  
  4.                    size_t count)  
  5. {  
  6.     size_t len;  
  7.   
  8.     /* 
  9.      * We read from the log in two disjoint operations. First, we read from 
  10.      * the current read head offset up to 'count' bytes or to the end of 
  11.      * the log, whichever comes first. 
  12.      */  
  13.     len = min(count, log->size - reader->r_off);  
  14.     if (copy_to_user(buf, log->buffer + reader->r_off, len))  
  15.         return -EFAULT;  
  16.   
  17.     /* 
  18.      * Second, we read any remaining bytes, starting back at the head of 
  19.      * the log. 
  20.      */  
  21.     if (count != len)  
  22.         if (copy_to_user(buf + len, log->buffer, count - len))  
  23.             return -EFAULT;  
  24.   
  25.     reader->r_off = logger_offset(reader->r_off + count);  
  26.   
  27.     return count;  
  28. }  
        这个函数简单地调用copy_to_user函数来把位于内核空间的日志缓冲区指定的内容拷贝到用户空间的内存缓冲区就可以了,同时,把当前读取日志进程的上下文信息中的读偏移r_off前进到下一条日志记录的开始的位置上。

        四.  Logger驱动程序的日志记录写入过程分析。

        继续看logger.c 文件,注册的写入日志设备文件的方法为logger_aio_write:

[cpp]  view plain copy
  1. /* 
  2.  * logger_aio_write - our write method, implementing support for write(), 
  3.  * writev(), and aio_write(). Writes are our fast path, and we try to optimize 
  4.  * them above all else. 
  5.  */  
  6. ssize_t logger_aio_write(struct kiocb *iocb, const struct iovec *iov,  
  7.              unsigned long nr_segs, loff_t ppos)  
  8. {  
  9.     struct logger_log *log = file_get_log(iocb->ki_filp);  
  10.     size_t orig = log->w_off;  
  11.     struct logger_entry header;  
  12.     struct timespec now;  
  13.     ssize_t ret = 0;  
  14.   
  15.     now = current_kernel_time();  
  16.   
  17.     header.pid = current->tgid;  
  18.     header.tid = current->pid;  
  19.     header.sec = now.tv_sec;  
  20.     header.nsec = now.tv_nsec;  
  21.     header.len = min_t(size_t, iocb->ki_left, LOGGER_ENTRY_MAX_PAYLOAD);  
  22.   
  23.     /* null writes succeed, return zero */  
  24.     if (unlikely(!header.len))  
  25.         return 0;  
  26.   
  27.     mutex_lock(&log->mutex);  
  28.   
  29.     /* 
  30.      * Fix up any readers, pulling them forward to the first readable 
  31.      * entry after (what will be) the new write offset. We do this now 
  32.      * because if we partially fail, we can end up with clobbered log 
  33.      * entries that encroach on readable buffer. 
  34.      */  
  35.     fix_up_readers(log, sizeof(struct logger_entry) + header.len);  
  36.   
  37.     do_write_log(log, &header, sizeof(struct logger_entry));  
  38.   
  39.     while (nr_segs-- > 0) {  
  40.         size_t len;  
  41.         ssize_t nr;  
  42.   
  43.         /* figure out how much of this vector we can keep */  
  44.         len = min_t(size_t, iov->iov_len, header.len - ret);  
  45.   
  46.         /* write out this segment's payload */  
  47.         nr = do_write_log_from_user(log, iov->iov_base, len);  
  48.         if (unlikely(nr < 0)) {  
  49.             log->w_off = orig;  
  50.             mutex_unlock(&log->mutex);  
  51.             return nr;  
  52.         }  
  53.   
  54.         iov++;  
  55.         ret += nr;  
  56.     }  
  57.   
  58.     mutex_unlock(&log->mutex);  
  59.   
  60.     /* wake up any blocked readers */  
  61.     wake_up_interruptible(&log->wq);  
  62.   
  63.     return ret;  
  64. }  
        输入的参数iocb表示io上下文,iov表示要写入的内容,长度为nr_segs,表示有nr_segs个段的内容要写入。我们知道,每个要写入的日志的结构形式为:

        struct logger_entry | priority | tag | msg

        其中, priority、tag和msg这三个段的内容是由iov参数从用户空间传递下来的,分别对应iov里面的三个元素。而logger_entry是由内核空间来构造的:

        struct logger_entry header;
struct timespec now;

now = current_kernel_time();

header.pid = current->tgid;
header.tid = current->pid;
header.sec = now.tv_sec;
header.nsec = now.tv_nsec;
header.len = min_t(size_t, iocb->ki_left, LOGGER_ENTRY_MAX_PAYLOAD);

        然后调用do_write_log首先把logger_entry结构体写入到日志缓冲区中:

[cpp]  view plain copy
  1. /* 
  2.  * do_write_log - writes 'len' bytes from 'buf' to 'log' 
  3.  * 
  4.  * The caller needs to hold log->mutex. 
  5.  */  
  6. static void do_write_log(struct logger_log *log, const void *buf, size_t count)  
  7. {  
  8.     size_t len;  
  9.   
  10.     len = min(count, log->size - log->w_off);  
  11.     memcpy(log->buffer + log->w_off, buf, len);  
  12.   
  13.     if (count != len)  
  14.         memcpy(log->buffer, buf + len, count - len);  
  15.   
  16.     log->w_off = logger_offset(log->w_off + count);  
  17.   
  18. }  
       由于logger_entry是内核堆栈空间分配的,直接用memcpy拷贝就可以了。

       接着,通过一个while循环把iov的内容写入到日志缓冲区中,也就是日志的优先级别priority、日志Tag和日志主体Msg:

[cpp]  view plain copy
  1. while (nr_segs-- > 0) {  
  2.         size_t len;  
  3.         ssize_t nr;  
  4.   
  5.         /* figure out how much of this vector we can keep */  
  6.         len = min_t(size_t, iov->iov_len, header.len - ret);  
  7.   
  8.         /* write out this segment's payload */  
  9.         nr = do_write_log_from_user(log, iov->iov_base, len);  
  10.         if (unlikely(nr < 0)) {  
  11.             log->w_off = orig;  
  12.             mutex_unlock(&log->mutex);  
  13.             return nr;  
  14.         }  
  15.   
  16.         iov++;  
  17.         ret += nr;  
  18. }  
         由于iov的内容是由用户空间传下来的,需要调用do_write_log_from_user来写入:

[cpp]  view plain copy
  1. static ssize_t do_write_log_from_user(struct logger_log *log,  
  2.                       const void __user *buf, size_t count)  
  3. {  
  4.     size_t len;  
  5.   
  6.     len = min(count, log->size - log->w_off);  
  7.     if (len && copy_from_user(log->buffer + log->w_off, buf, len))  
  8.         return -EFAULT;  
  9.   
  10.     if (count != len)  
  11.         if (copy_from_user(log->buffer, buf + len, count - len))  
  12.             return -EFAULT;  
  13.   
  14.     log->w_off = logger_offset(log->w_off + count);  
  15.   
  16.     return count;  
  17. }  
        这里,我们还漏了一个重要的步骤:

[cpp]  view plain copy
  1.  /* 
  2.   * Fix up any readers, pulling them forward to the first readable 
  3.   * entry after (what will be) the new write offset. We do this now 
  4.   * because if we partially fail, we can end up with clobbered log 
  5.   * entries that encroach on readable buffer. 
  6.   */  
  7. fix_up_readers(log, sizeof(struct logger_entry) + header.len);  
        为什么要调用fix_up_reader这个函数呢?这个函数又是作什么用的呢?是这样的,由于日志缓冲区是循环使用的,即旧的日志记录如果没有及时读取,而缓冲区的内容又已经用完时,就需要覆盖旧的记录来容纳新的记录。而这部分将要被覆盖的内容,有可能是某些reader的下一次要读取的日志所在的位置,以及为新的reader准备的日志开始读取位置head所在的位置。因此,需要调整这些位置,使它们能够指向一个新的有效的位置。我们来看一下fix_up_reader函数的实现:

[cpp]  view plain copy
  1. /* 
  2.  * fix_up_readers - walk the list of all readers and "fix up" any who were 
  3.  * lapped by the writer; also do the same for the default "start head". 
  4.  * We do this by "pulling forward" the readers and start head to the first 
  5.  * entry after the new write head. 
  6.  * 
  7.  * The caller needs to hold log->mutex. 
  8.  */  
  9. static void fix_up_readers(struct logger_log *log, size_t len)  
  10. {  
  11.     size_t old = log->w_off;  
  12.     size_t new = logger_offset(old + len);  
  13.     struct logger_reader *reader;  
  14.   
  15.     if (clock_interval(old, new, log->head))  
  16.         log->head = get_next_entry(log, log->head, len);  
  17.   
  18.     list_for_each_entry(reader, &log->readers, list)  
  19.         if (clock_interval(old, new, reader->r_off))  
  20.             reader->r_off = get_next_entry(log, reader->r_off, len);  
  21. }  
        判断log->head和所有读者reader的当前读偏移reader->r_off是否在被覆盖的区域内,如果是,就需要调用get_next_entry来取得下一个有效的记录的起始位置来调整当前位置:

[cpp]  view plain copy
  1. /* 
  2.  * get_next_entry - return the offset of the first valid entry at least 'len' 
  3.  * bytes after 'off'. 
  4.  * 
  5.  * Caller must hold log->mutex. 
  6.  */  
  7. static size_t get_next_entry(struct logger_log *log, size_t off, size_t len)  
  8. {  
  9.     size_t count = 0;  
  10.   
  11.     do {  
  12.         size_t nr = get_entry_len(log, off);  
  13.         off = logger_offset(off + nr);  
  14.         count += nr;  
  15.     } while (count < len);  
  16.   
  17.     return off;  
  18. }  
        而判断log->head和所有读者reader的当前读偏移reader->r_off是否在被覆盖的区域内,是通过clock_interval函数来实现的:

[cpp]  view plain copy
  1. /* 
  2.  * clock_interval - is a < c < b in mod-space? Put another way, does the line 
  3.  * from a to b cross c? 
  4.  */  
  5. static inline int clock_interval(size_t a, size_t b, size_t c)  
  6. {  
  7.     if (b < a) {  
  8.         if (a < c || b >= c)  
  9.             return 1;  
  10.     } else {  
  11.         if (a < c && b >= c)  
  12.             return 1;  
  13.     }  
  14.   
  15.     return 0;  
  16. }  
        最后,日志写入完毕,还需要唤醒正在等待新日志的reader进程:

        /* wake up any blocked readers */
wake_up_interruptible(&log->wq);

        至此, Logger驱动程序的主要逻辑就分析完成了,还有其它的一些接口,如logger_poll、 logger_ioctl和logger_release函数,比较简单,读取可以自行分析。这里还需要提到的一点是,由于Logger驱动程序模块在退出系统时,是不会卸载的,所以这个模块没有module_exit函数,而对于模块里面定义的对象,也没有用对引用计数技术。

      这篇文章着重介绍了Android日志系统在内核空间的实现,在下一篇文章中,我们将接着介绍在用户空间中,提供给Android应用程序使用的Java和C/C++ LOG调用接口的实现过程,敬请关注。

相关实践学习
日志服务之使用Nginx模式采集日志
本文介绍如何通过日志服务控制台创建Nginx模式的Logtail配置快速采集Nginx日志并进行多维度分析。
相关文章
|
10天前
|
开发框架 前端开发 Android开发
安卓与iOS开发中的跨平台策略
在移动应用开发的战场上,安卓和iOS两大阵营各据一方。随着技术的演进,跨平台开发框架成为开发者的新宠,旨在实现一次编码、多平台部署的梦想。本文将探讨跨平台开发的优势与挑战,并分享实用的开发技巧,帮助开发者在安卓和iOS的世界中游刃有余。
|
15天前
|
搜索推荐 Android开发 开发者
探索安卓开发中的自定义视图:打造个性化UI组件
【10月更文挑战第39天】在安卓开发的世界中,自定义视图是实现独特界面设计的关键。本文将引导你理解自定义视图的概念、创建流程,以及如何通过它们增强应用的用户体验。我们将从基础出发,逐步深入,最终让你能够自信地设计和实现专属的UI组件。
|
17天前
|
Android开发 Swift iOS开发
探索安卓与iOS开发的差异和挑战
【10月更文挑战第37天】在移动应用开发的广阔舞台上,安卓和iOS这两大操作系统扮演着主角。它们各自拥有独特的特性、优势以及面临的开发挑战。本文将深入探讨这两个平台在开发过程中的主要差异,从编程语言到用户界面设计,再到市场分布的不同影响,旨在为开发者提供一个全面的视角,帮助他们更好地理解并应对在不同平台上进行应用开发时可能遇到的难题和机遇。
|
19天前
|
XML 存储 Java
探索安卓开发之旅:从新手到专家
【10月更文挑战第35天】在数字化时代,安卓应用的开发成为了一个热门话题。本文旨在通过浅显易懂的语言,带领初学者了解安卓开发的基础知识,同时为有一定经验的开发者提供进阶技巧。我们将一起探讨如何从零开始构建第一个安卓应用,并逐步深入到性能优化和高级功能的实现。无论你是编程新手还是希望提升技能的开发者,这篇文章都将为你提供有价值的指导和灵感。
|
17天前
|
存储 API 开发工具
探索安卓开发:从基础到进阶
【10月更文挑战第37天】在这篇文章中,我们将一起探索安卓开发的奥秘。无论你是初学者还是有经验的开发者,这篇文章都将为你提供有价值的信息和建议。我们将从安卓开发的基础开始,逐步深入到更复杂的主题,如自定义组件、性能优化等。最后,我们将通过一个代码示例来展示如何实现一个简单的安卓应用。让我们一起开始吧!
|
18天前
|
存储 XML JSON
探索安卓开发:从新手到专家的旅程
【10月更文挑战第36天】在这篇文章中,我们将一起踏上一段激动人心的旅程,从零基础开始,逐步深入安卓开发的奥秘。无论你是编程新手,还是希望扩展技能的老手,这里都有适合你的知识宝藏等待发掘。通过实际的代码示例和深入浅出的解释,我们将解锁安卓开发的关键技能,让你能够构建自己的应用程序,甚至贡献于开源社区。准备好了吗?让我们开始吧!
26 2
|
19天前
|
Android开发
布谷语音软件开发:android端语音软件搭建开发教程
语音软件搭建android端语音软件开发教程!
|
27天前
|
编解码 Java Android开发
通义灵码:在安卓开发中提升工作效率的真实应用案例
本文介绍了通义灵码在安卓开发中的应用。作为一名97年的聋人开发者,我在2024年Google Gemma竞赛中获得了冠军,拿下了很多项目竞赛奖励,通义灵码成为我的得力助手。文章详细展示了如何安装通义灵码插件,并通过多个实例说明其在适配国际语言、多种分辨率、业务逻辑开发和编程语言转换等方面的应用,显著提高了开发效率和准确性。
|
25天前
|
Android开发 开发者 UED
安卓开发中自定义View的实现与性能优化
【10月更文挑战第28天】在安卓开发领域,自定义View是提升应用界面独特性和用户体验的重要手段。本文将深入探讨如何高效地创建和管理自定义View,以及如何通过代码和性能调优来确保流畅的交互体验。我们将一起学习自定义View的生命周期、绘图基础和事件处理,进而探索内存和布局优化技巧,最终实现既美观又高效的安卓界面。
32 5
|
24天前
|
JSON Java Android开发
探索安卓开发之旅:打造你的第一个天气应用
【10月更文挑战第30天】在这个数字时代,掌握移动应用开发技能无疑是进入IT行业的敲门砖。本文将引导你开启安卓开发的奇妙之旅,通过构建一个简易的天气应用来实践你的编程技能。无论你是初学者还是有一定经验的开发者,这篇文章都将成为你宝贵的学习资源。我们将一步步地深入到安卓开发的世界中,从搭建开发环境到实现核心功能,每个环节都充满了发现和创造的乐趣。让我们开始吧,一起在代码的海洋中航行!