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

文章转载至CSDN社区罗升阳的安卓之旅,原文地址:http://blog.csdn.net/luoshengyang/article/details/6595744

我们知道,在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 plaincopy

  1. #ifndef _LINUX_LOGGER_H
  2. #define _LINUX_LOGGER_H
  3. #include <linux/types.h>
  4. #include <linux/ioctl.h>
  5. struct logger_entry {
  6. __u16       len;    /* length of the payload */
  7. __u16       __pad;  /* no matter what, we get 2 bytes of padding */
  8. __s32       pid;    /* generating process‘s pid */
  9. __s32       tid;    /* generating process‘s tid */
  10. __s32       sec;    /* seconds since Epoch */
  11. __s32       nsec;   /* nanoseconds */
  12. char        msg[0]; /* the entry‘s payload */
  13. };
  14. #define LOGGER_LOG_RADIO    "log_radio" /* radio-related messages */
  15. #define LOGGER_LOG_EVENTS   "log_events"    /* system/hardware events */
  16. #define LOGGER_LOG_MAIN     "log_main"  /* everything else */
  17. #define LOGGER_ENTRY_MAX_LEN        (4*1024)
  18. #define LOGGER_ENTRY_MAX_PAYLOAD    \
  19. (LOGGER_ENTRY_MAX_LEN - sizeof(struct logger_entry))
  20. #define __LOGGERIO  0xAE
  21. #define LOGGER_GET_LOG_BUF_SIZE     _IO(__LOGGERIO, 1) /* size of log */
  22. #define LOGGER_GET_LOG_LEN      _IO(__LOGGERIO, 2) /* used log len */
  23. #define LOGGER_GET_NEXT_ENTRY_LEN   _IO(__LOGGERIO, 3) /* next entry len */
  24. #define LOGGER_FLUSH_LOG        _IO(__LOGGERIO, 4) /* flush log */
  25. #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 plaincopy

  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. * struct logger_reader - a logging device open for reading
  20. *
  21. * This object lives from open to release, so we don‘t need additional
  22. * reference counting. The structure is protected by log->mutex.
  23. */
  24. struct logger_reader {
  25. struct logger_log * log;    /* associated log */
  26. struct list_head    list;   /* entry in logger_log‘s list */
  27. size_t          r_off;  /* current read head offset */
  28. };
  29. /* logger_offset - returns index ‘n‘ into the log via (optimized) modulus */
  30. #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 plaincopy

  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. DEFINE_LOGGER_DEVICE(log_main, LOGGER_LOG_MAIN, 64*1024)
  24. DEFINE_LOGGER_DEVICE(log_events, LOGGER_LOG_EVENTS, 256*1024)
  25. 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 plaincopy

  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 plaincopy

  1. static int __init logger_init(void)
  2. {
  3. int ret;
  4. ret = init_log(&log_main);
  5. if (unlikely(ret))
  6. goto out;
  7. ret = init_log(&log_events);
  8. if (unlikely(ret))
  9. goto out;
  10. ret = init_log(&log_radio);
  11. if (unlikely(ret))
  12. goto out;
  13. out:
  14. return ret;
  15. }
  16. device_initcall(logger_init);

logger_init函数通过调用init_log函数来初始化了上述提到的三个日志设备:

[cpp] view plaincopy

  1. static int __init init_log(struct logger_log *log)
  2. {
  3. int ret;
  4. ret = misc_register(&log->misc);
  5. if (unlikely(ret)) {
  6. printk(KERN_ERR "logger: failed to register misc "
  7. "device for log ‘%s‘!\n", log->misc.name);
  8. return ret;
  9. }
  10. printk(KERN_INFO "logger: created %luK log ‘%s‘\n",
  11. (unsigned long) log->size >> 10, log->misc.name);
  12. return 0;
  13. }

init_log函数主要调用了misc_register函数来注册misc设备,misc_register函数定义在kernel/common/drivers/char/misc.c文件中:

[cpp] view plaincopy

  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. int misc_register(struct miscdevice * misc)
  17. {
  18. struct miscdevice *c;
  19. dev_t dev;
  20. int err = 0;
  21. INIT_LIST_HEAD(&misc->list);
  22. mutex_lock(&misc_mtx);
  23. list_for_each_entry(c, &misc_list, list) {
  24. if (c->minor == misc->minor) {
  25. mutex_unlock(&misc_mtx);
  26. return -EBUSY;
  27. }
  28. }
  29. if (misc->minor == MISC_DYNAMIC_MINOR) {
  30. int i = DYNAMIC_MINORS;
  31. while (--i >= 0)
  32. if ( (misc_minors[i>>3] & (1 << (i&7))) == 0)
  33. break;
  34. if (i<0) {
  35. mutex_unlock(&misc_mtx);
  36. return -EBUSY;
  37. }
  38. misc->minor = i;
  39. }
  40. if (misc->minor < DYNAMIC_MINORS)
  41. misc_minors[misc->minor >> 3] |= 1 << (misc->minor & 7);
  42. dev = MKDEV(MISC_MAJOR, misc->minor);
  43. misc->this_device = device_create(misc_class, misc->parent, dev, NULL,
  44. "%s", misc->name);
  45. if (IS_ERR(misc->this_device)) {
  46. err = PTR_ERR(misc->this_device);
  47. goto out;
  48. }
  49. /*
  50. * Add it to the front, so that later devices can "override"
  51. * earlier defaults
  52. */
  53. list_add(&misc->list, &misc_list);
  54. out:
  55. mutex_unlock(&misc_mtx);
  56. return err;
  57. }

注册完成后,通过device_create创建设备文件节点。这里,将创建/dev/log/main、/dev/log/events和/dev/log/radio三个设备文件,这样,用户空间就可以通过读写这三个文件和驱动程序进行交互。

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

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

[cpp] view plaincopy

  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. start:
  21. while (1) {
  22. prepare_to_wait(&log->wq, &wait, TASK_INTERRUPTIBLE);
  23. mutex_lock(&log->mutex);
  24. ret = (log->w_off == reader->r_off);
  25. mutex_unlock(&log->mutex);
  26. if (!ret)
  27. break;
  28. if (file->f_flags & O_NONBLOCK) {
  29. ret = -EAGAIN;
  30. break;
  31. }
  32. if (signal_pending(current)) {
  33. ret = -EINTR;
  34. break;
  35. }
  36. schedule();
  37. }
  38. finish_wait(&log->wq, &wait);
  39. if (ret)
  40. return ret;
  41. mutex_lock(&log->mutex);
  42. /* is there still something to read or did we race? */
  43. if (unlikely(log->w_off == reader->r_off)) {
  44. mutex_unlock(&log->mutex);
  45. goto start;
  46. }
  47. /* get the size of the next entry */
  48. ret = get_entry_len(log, reader->r_off);
  49. if (count < ret) {
  50. ret = -EINVAL;
  51. goto out;
  52. }
  53. /* get exactly one entry from the log */
  54. ret = do_read_log_to_user(log, reader, buf, ret);
  55. out:
  56. mutex_unlock(&log->mutex);
  57. return ret;
  58. }

注意,在函数开始的地方,表示读取日志上下文的struct logger_reader是保存在文件指针的private_data成员变量里面的,这是在打开设备文件时设置的,设备文件打开方法为logger_open:

[cpp] view plaincopy

  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. ret = nonseekable_open(inode, file);
  11. if (ret)
  12. return ret;
  13. log = get_log_from_minor(MINOR(inode->i_rdev));
  14. if (!log)
  15. return -ENODEV;
  16. if (file->f_mode & FMODE_READ) {
  17. struct logger_reader *reader;
  18. reader = kmalloc(sizeof(struct logger_reader), GFP_KERNEL);
  19. if (!reader)
  20. return -ENOMEM;
  21. reader->log = log;
  22. INIT_LIST_HEAD(&reader->list);
  23. mutex_lock(&log->mutex);
  24. reader->r_off = log->head;
  25. list_add_tail(&reader->list, &log->readers);
  26. mutex_unlock(&log->mutex);
  27. file->private_data = reader;
  28. } else
  29. file->private_data = log;
  30. return 0;
  31. }

新打开日志设备文件时,是从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 plaincopy

  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. switch (log->size - off) {
  11. case 1:
  12. memcpy(&val, log->buffer + off, 1);
  13. memcpy(((char *) &val) + 1, log->buffer, 1);
  14. break;
  15. default:
  16. memcpy(&val, log->buffer + off, 2);
  17. }
  18. return sizeof(struct logger_entry) + val;
  19. }

上面我们提到,每一条日志记录是由两大部分组成的,一个用于描述这条日志记录的结构体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 plaincopy

  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. * We read from the log in two disjoint operations. First, we read from
  9. * the current read head offset up to ‘count‘ bytes or to the end of
  10. * the log, whichever comes first.
  11. */
  12. len = min(count, log->size - reader->r_off);
  13. if (copy_to_user(buf, log->buffer + reader->r_off, len))
  14. return -EFAULT;
  15. /*
  16. * Second, we read any remaining bytes, starting back at the head of
  17. * the log.
  18. */
  19. if (count != len)
  20. if (copy_to_user(buf + len, log->buffer, count - len))
  21. return -EFAULT;
  22. reader->r_off = logger_offset(reader->r_off + count);
  23. return count;
  24. }

这个函数简单地调用copy_to_user函数来把位于内核空间的日志缓冲区指定的内容拷贝到用户空间的内存缓冲区就可以了,同时,把当前读取日志进程的上下文信息中的读偏移r_off前进到下一条日志记录的开始的位置上。

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

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

[cpp] view plaincopy

  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. now = current_kernel_time();
  15. header.pid = current->tgid;
  16. header.tid = current->pid;
  17. header.sec = now.tv_sec;
  18. header.nsec = now.tv_nsec;
  19. header.len = min_t(size_t, iocb->ki_left, LOGGER_ENTRY_MAX_PAYLOAD);
  20. /* null writes succeed, return zero */
  21. if (unlikely(!header.len))
  22. return 0;
  23. mutex_lock(&log->mutex);
  24. /*
  25. * Fix up any readers, pulling them forward to the first readable
  26. * entry after (what will be) the new write offset. We do this now
  27. * because if we partially fail, we can end up with clobbered log
  28. * entries that encroach on readable buffer.
  29. */
  30. fix_up_readers(log, sizeof(struct logger_entry) + header.len);
  31. do_write_log(log, &header, sizeof(struct logger_entry));
  32. while (nr_segs-- > 0) {
  33. size_t len;
  34. ssize_t nr;
  35. /* figure out how much of this vector we can keep */
  36. len = min_t(size_t, iov->iov_len, header.len - ret);
  37. /* write out this segment‘s payload */
  38. nr = do_write_log_from_user(log, iov->iov_base, len);
  39. if (unlikely(nr < 0)) {
  40. log->w_off = orig;
  41. mutex_unlock(&log->mutex);
  42. return nr;
  43. }
  44. iov++;
  45. ret += nr;
  46. }
  47. mutex_unlock(&log->mutex);
  48. /* wake up any blocked readers */
  49. wake_up_interruptible(&log->wq);
  50. return ret;
  51. }

输入的参数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 plaincopy

  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. len = min(count, log->size - log->w_off);
  10. memcpy(log->buffer + log->w_off, buf, len);
  11. if (count != len)
  12. memcpy(log->buffer, buf + len, count - len);
  13. log->w_off = logger_offset(log->w_off + count);
  14. }

由于logger_entry是内核堆栈空间分配的,直接用memcpy拷贝就可以了。

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

[cpp] view plaincopy

  1. while (nr_segs-- > 0) {
  2. size_t len;
  3. ssize_t nr;
  4. /* figure out how much of this vector we can keep */
  5. len = min_t(size_t, iov->iov_len, header.len - ret);
  6. /* write out this segment‘s payload */
  7. nr = do_write_log_from_user(log, iov->iov_base, len);
  8. if (unlikely(nr < 0)) {
  9. log->w_off = orig;
  10. mutex_unlock(&log->mutex);
  11. return nr;
  12. }
  13. iov++;
  14. ret += nr;
  15. }

由于iov的内容是由用户空间传下来的,需要调用do_write_log_from_user来写入:

[cpp] view plaincopy

  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. len = min(count, log->size - log->w_off);
  6. if (len && copy_from_user(log->buffer + log->w_off, buf, len))
  7. return -EFAULT;
  8. if (count != len)
  9. if (copy_from_user(log->buffer, buf + len, count - len))
  10. return -EFAULT;
  11. log->w_off = logger_offset(log->w_off + count);
  12. return count;
  13. }

这里,我们还漏了一个重要的步骤:

[cpp] view plaincopy

  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 plaincopy

  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. if (clock_interval(old, new, log->head))
  15. log->head = get_next_entry(log, log->head, len);
  16. list_for_each_entry(reader, &log->readers, list)
  17. if (clock_interval(old, new, reader->r_off))
  18. reader->r_off = get_next_entry(log, reader->r_off, len);
  19. }

判断log->head和所有读者reader的当前读偏移reader->r_off是否在被覆盖的区域内,如果是,就需要调用get_next_entry来取得下一个有效的记录的起始位置来调整当前位置:

[cpp] view plaincopy

  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. do {
  11. size_t nr = get_entry_len(log, off);
  12. off = logger_offset(off + nr);
  13. count += nr;
  14. } while (count < len);
  15. return off;
  16. }

而判断log->head和所有读者reader的当前读偏移reader->r_off是否在被覆盖的区域内,是通过clock_interval函数来实现的:

[cpp] view plaincopy

  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. return 0;
  15. }

最后,日志写入完毕,还需要唤醒正在等待新日志的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调用接口的实现过程,敬请关注。

老罗的新浪微博:http://weibo.com/shengyangluo,欢迎关注!

时间: 2024-10-21 17:56:33

Android日志系统驱动程序Logger源代码分析的相关文章

Android日志系统Logcat源代码简要分析

文章转载至CSDN社区罗升阳的安卓之旅,原文地址:http://blog.csdn.net/luoshengyang/article/details/6606957 在前面两篇文章Android日志系统驱动程序Logger源代码分析和Android应用程序框架层和系统运行库层日志系统源代码中,介绍了Android内核空间层.系统运行库层和应用程序框架层日志系统相关的源代码,其中,后一篇文章着重介绍了日志的写入操作.为了描述完整性,这篇文章着重介绍日志的读取操作,这就是我们在开发Android应用

Android 属性系统 Property service 设定分析 (转载)

转自:http://blog.csdn.net/andyhuabing/article/details/7381879 Android 属性系统 Property service 设定分析 在Window中有个注册表的东东,可以存储一些类似key:value的 键值对,而在android平台上也有类似的机制叫做属性服务(Property service)进行初始化,设置及修改和查询的功能,adb shell命令使用 setprop 及 getprop 可以看到. 问题: SurfaceFling

Android应用程序安装过程源代码分析

文章转载至CSDN社区罗升阳的安卓之旅,原文地址:http://blog.csdn.net/luoshengyang/article/details/6766010 Android系统在启动的过程中,会启动一个应用程序管理服务PackageManagerService,这个服务 负责扫描系统中特定的目录,找到里面的应用程序文件,即以Apk为后缀的文件,然后对这些文件进解析,得到应用程序的相关信息,完成应用程序的安装过程, 本文将详细分析这个过程. 应用程序管理服务PackageManagerSe

Android万能适配器base-adapter-helper的源代码分析

项目地址:https://github.com/JoanZapata/base-adapter-helper 1. 功能介绍 1.1. base-adapter-helper base-adapter-helper 是对传统的 BaseAdapter ViewHolder 模式的一个封装.主要功能就是简化我们书写 AbsListView 的 Adapter 的代码,如 ListView,GridView. 1.2 基本使用 mListView.setAdapter(mAdapter = new

《Android笔记3.11》 Android 日志系统

课程背景:Android 日志是用来记录程序运行过程的,但是在实际开发中,由于日志信息太多导致不方便查看有效日志而影响了正常的开发调试工作,所以学会对日志进行分类查看非常重要. 核心内容:1.System.out2.System.err3.Android Log 类4.日志分类过滤 使用日志 API System.out.println("普通日志Info"); System.err.println("错误日志Warning"); Log.e(TAG, "

Android应用程序启动过程源代码分析

文章转载至CSDN社区罗升阳的安卓之旅,原文地址:http://blog.csdn.net/luoshengyang/article/details/6689748 前文简要介绍了Android应用程序的Activity的启动过程.在Android系统中,应用程序是由Activity组成的,因此,应用程 序的启动过程实际上就是应用程序中的默认Activity的启动过程,本文将详细分析应用程序框架层的源代码,了解Android应用程序的启动过程. 在上一篇文章Android应用程序的Activit

Android 日志记录杂谈-Logger,Timber,logback-android

转载:http://www.jianshu.com/p/39834be3cb6c "Every time you log in production, a puppy dies." – Jake Wharton 做安卓项目中,调试程序的时候一般会进行打log来查看相关信息,而我原来是使用的系统自带的Log来打印. 归结二个问题:<信息显示>,<信息存储>,就这二个问题我们具体来看如何解决 -----------------------主体分割君---------

Android 的系统属性(SystemProperties)设置分析

1. JAVA 层与内部so之间调用 java层的权限,KEY,value的定义规则与要求 http://www.blogjava.net/anymobile/articles/301989.html Android SystemProperties设置/取得系统属性的用法总结 http://blog.csdn.net/ameyume/article/details/8056492 1.Android SystemProperties简介 介绍了设置属性需要的权限,已经设置权限的方法. Syst

Android应用程序框架层和系统运行库层日志系统源代码分析

文章转载至CSDN社区罗升阳的安卓之旅,原文地址:http://blog.csdn.net/luoshengyang/article/details/6598703 在开发Android应用程序时,少不了使用Log来监控和调试程序的执行.在上一篇文章Android日志系统驱动程序Logger源代码分析中,我们分析了驱动程序Logger的源代码,在前面的文章浅谈Android系统开发中Log的使用一文,我们也简单介绍在应用程序中使Log的方法,在这篇文章中,我们将详细介绍Android应用程序框架