Logger详解(四)

来源:互联网 发布:程序员加班猝死 编辑:程序博客网 时间:2024/06/10 10:51
本文编辑整理自:http://www.linuxidc.com/Linux/2011-07/38987.htm
五、日志记录写入过程分析
继续看kernel/drivers/staging/Android/logger.c的文件
注册的写入日志设备文件的方法为logger_aio_write:
 /* 
 * logger_aio_write - our write method, implementing support for write(), 
 * writev(), and aio_write(). Writes are our fast path, and we try to optimize 
 * them above all else. 
 */  
ssize_t logger_aio_write(struct kiocb *iocb, const struct iovec *iov,  
             unsigned long nr_segs, loff_t ppos)  
{  
    struct logger_log *log = file_get_log(iocb->ki_filp);  
    size_t orig = log->w_off;  
    struct logger_entry header;  
    struct timespec now;  
    ssize_t ret = 0;  
  
    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);  
  
    /* null writes succeed, return zero */  
    if (unlikely(!header.len))  
        return 0;  
  
    mutex_lock(&log->mutex);  
  
    /*
     * Fix up any readers, pulling them forward to the first readable 
     * entry after (what will be) the new write offset. We do this now 
     * because if we partially fail, we can end up with clobbered log 
     * entries that encroach on readable buffer. 
     */  
    fix_up_readers(log, sizeof(struct logger_entry) + header.len);  
  
    do_write_log(log, &header, sizeof(struct logger_entry));  
  
    while (nr_segs-- > 0) {  
        size_t len;  
        ssize_t nr;  
  
        /* figure out how much of this vector we can keep */  
        len = min_t(size_t, iov->iov_len, header.len - ret);  
  
        /* write out this segment's payload */  
        nr = do_write_log_from_user(log, iov->iov_base, len);  
        if (unlikely(nr < 0)) {  
            log->w_off = orig;  
            mutex_unlock(&log->mutex);  
            return nr;  
        }  
  
        iov++;  
        ret += nr;  
    }  
  
    mutex_unlock(&log->mutex);  
  
    /* wake up any blocked readers */  
    wake_up_interruptible(&log->wq);  
  
    return ret;  
}  
输入的参数iocb表示io上下文,iov表示要写入的内容,长度为nr_segs,表示有nr_segs个段的内容要写入。
我们知道,每个要写入的日志的结构形式为:
        结构体logger_entrypriority+tag+msg
        其中, prioritytagmsg这三个段的内容是由iov参数从用户空间传递下来的,分别对应iov里面的三个元素。而logger_entry是在logger_aio_write()函数中构造的.
然后调用do_write_log首先把logger_entry结构体写入到日志缓冲区中:
/* 
 * do_write_log - writes 'len' bytes from 'buf' to 'log' 
 * 
 * The caller needs to hold log->mutex. 
 */  
static void do_write_log(struct logger_log *log, const void *buf, size_t count)  
{  
    size_t len;  
  
    len = min(count, log->size - log->w_off);  
    memcpy(log->buffer + log->w_off, buf, len);  
  
    if (count != len)  
        memcpy(log->buffer, buf + len, count - len);  
  
    log->w_off = logger_offset(log->w_off + count);  
  
}  
    由于logger_entry是内核堆栈空间分配的,直接用memcpy拷贝就可以了。
接着,通过一个while循环把iov的内容写入到日志缓冲区中,也就是日志的优先级别priority日志Tag日志主体Msg
由于iov的内容是由用户空间传下来的,需要调用do_write_log_from_user来写入:
static ssize_t do_write_log_from_user(struct logger_log *log,  
                      const void __user *buf, size_t count)  
{  
    size_t len;  
  
    len = min(count, log->size - log->w_off);  
    if (len && copy_from_user(log->buffer + log->w_off, buf, len))  
        return -EFAULT;  
  
    if (count != len)  
        if (copy_from_user(log->buffer, buf + len, count - len))  
            return -EFAULT;  
  
    log->w_off = logger_offset(log->w_off + count);  
  
    return count;  
}  
上面,我们还漏了一个重要的步骤:fix_up_readers()函数的调用。为什么要调用它呢?是这样的,由于日志缓冲区是循环使用的,即旧的日志记录如果没有及时读取,而缓冲区的内容又已经用完时,就需要覆盖旧的记录来容纳新的记录。而这部分将要被覆盖的内容,有可能是某些reader的下一次要读取的日志所在的位置,以及为新的reader准备的日志开始读取位置head所在的位置。因此,需要调整这些reader的读取位置,使它们能够指向一个新的有效的位置。我们来看一下fix_up_reader函数的实现:
/* 
 * fix_up_readers - walk the list of all readers and "fix up" any who were 
 * lapped by the writer; also do the same for the default "start head". 
 * We do this by "pulling forward" the readers and start head to the first 
 * entry after the new write head. 
 * 
 * The caller needs to hold log->mutex. 
 */  
static void fix_up_readers(struct logger_log *log, size_t len)  
{  
    size_t old = log->w_off;  
    size_t new = logger_offset(old + len);  
    struct logger_reader *reader;  
  
    if (clock_interval(old, new, log->head))  
        log->head = get_next_entry(log, log->head, len);  
  
    list_for_each_entry(reader, &log->readers, list)  
        if (clock_interval(old, new, reader->r_off))  
            reader->r_off = get_next_entry(log, reader->r_off, len);  
}  
判断log->head和所有读者reader的当前读偏移reader->r_off是否在被覆盖的区域内,如果是,就需要调用get_next_entry()函数来取得下一个有效的记录的起始位置来调整当前位置
/* 
 * get_next_entry - return the offset of the first valid entry at least 'len' 
 * bytes after 'off'. 
 * 
 * Caller must hold log->mutex. 
 */  
static size_t get_next_entry(struct logger_log *log, size_t off, size_t len)  
{  
    size_t count = 0;  
  
    do {  
        size_t nr = get_entry_len(log, off);  
        off = logger_offset(off + nr);  
        count += nr;  
    } while (count < len);  
  
    return off;  
}  
而判断log->head和所有读者reader的当前读偏移reader->r_off是否在被覆盖的区域内,是通过clock_interval函数来实现的
/* 
 * clock_interval - is a < c < b in mod-space? Put another way, does the line 
 * from a to b cross c? 
 */  
static inline int clock_interval(size_t a, size_t b, size_t c)  
{  
    if (b < a) {  
        if (a < c || b >= c)  
            return 1;  
    } else {  
        if (a < c && b >= c)  
            return 1;  
    }  
  
    return 0;  
最后,日志写入完毕,还需要唤醒正在等待新日志的reader进程:
        /* wake up any blocked readers */
wake_up_interruptible(&log->wq);
        至此, Logger驱动程序的主要逻辑就分析完成了,还有其它的一些接口,如logger_poll、 logger_ioctl和logger_release函数,比较简单,读取可以自行分析。这里还需要提到的一点是,由于Logger驱动程序模块在退出系统时,是不会卸载的,所以这个模块没有module_exit函数,而对于模块里面定义的对象,也没有用对引用计数技术。