Logger详解(三)

来源:互联网 发布:程序员加班猝死 编辑:程序博客网 时间:2024/04/29 01:23
四、日志记录读取过程分析
继续看kernel/drivers/staging/Android/logger.c 文件,注册的读取日志设备文件的方法为logger_read: 
/* 
 * logger_read - our log's read() method 
 * 
 * Behavior: 
 * 
 *  - O_NONBLOCK works 
 *  - If there are no log entries to read, blocks until log is written to 
 *  - Atomically reads exactly one log entry 
 * 
 * Optimal read size is LOGGER_ENTRY_MAX_LEN. Will set errno to EINVAL if read 
 * buffer is insufficient to hold next entry. 
 */  
static ssize_t logger_read(struct file *file, char __user *buf,  
               size_t count, loff_t *pos)  
{  
    struct logger_reader *reader = file->private_data;  
    struct logger_log *log = reader->log;  
    ssize_t ret;  
    DEFINE_WAIT(wait);  
  
start:  
    while (1) {  
        prepare_to_wait(&log->wq, &wait, TASK_INTERRUPTIBLE);  
  
        mutex_lock(&log->mutex);  
        ret = (log->w_off == reader->r_off);  
        mutex_unlock(&log->mutex);  
        if (!ret)  
            break;  
  
        if (file->f_flags & O_NONBLOCK) {  
            ret = -EAGAIN;  
            break;  
        }  
  
        if (signal_pending(current)) {  
            ret = -EINTR;  
            break;  
        }  
  
        schedule();  
    }  
  
    finish_wait(&log->wq, &wait);  
    if (ret)  
        return ret;  
  
    mutex_lock(&log->mutex);  
  
    /* is there still something to read or did we race? */  
    if (unlikely(log->w_off == reader->r_off)) {  
        mutex_unlock(&log->mutex);  
        goto start;  
    }  
  
    /* get the size of the next entry */  
    ret = get_entry_len(log, reader->r_off);  
    if (count < ret) {  
        ret = -EINVAL;  
        goto out;  
    }  
  
    /* get exactly one entry from the log */  
    ret = do_read_log_to_user(log, reader, buf, ret);  
  
out:  
    mutex_unlock(&log->mutex);  
  
    return ret;  
}  
 注意在函数开始的地方,表示读取日志上下文的结构体logger_reader是保存在文件指针的private_data成员变量里面的,这是在打开设备文件时设置的,设备文件打开方法为logger_open
/* 
 * logger_open - the log's open() file operation 
 * 
 * Note how near a no-op this is in the write-only case. Keep it that way! 
 */  
static int logger_open(struct inode *inode, struct file *file)  
{  
    struct logger_log *log;  
    int ret;  
  
    ret = nonseekable_open(inode, file);  
    if (ret)  
        return ret;  
  
    log = get_log_from_minor(MINOR(inode->i_rdev));  
    if (!log)  
        return -ENODEV;  
  
    if (file->f_mode & FMODE_READ) {  
        struct logger_reader *reader;  
  
        reader = kmalloc(sizeof(struct logger_reader), GFP_KERNEL);  
        if (!reader)  
            return -ENOMEM;  
  
        reader->log = log;  
        INIT_LIST_HEAD(&reader->list);  
  
        mutex_lock(&log->mutex);  
        reader->r_off = log->head;  
        list_add_tail(&reader->list, &log->readers);  
        mutex_unlock(&log->mutex);  
  
        file->private_data = reader;  
    } else  
        file->private_data = log;  
  
    return 0;  
}  
我们可以使用get_log_from_minor()函数通过inode->i_rdev所对应对应的MINOR ID来返回不同的Looger驱动的指针。
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;
}
   新打开日志设备文件时,是从log->head位置开始读取日志的,保存在logger_reader的成员变量r_off中。
start标号处的while循环是在等待日志可读,如果已经没有新的日志可读了,那么就要读进程就要进入休眠状态,等待新的日志写入后再唤醒,这是通过prepare_waitschedule两个调用来实现的。如果没有新的日志可读,并且设备文件不是以非阻塞O_NONBLOCK的方式打开或者这时有信号要处理(signal_pending(current)),那么就直接返回,不再等待新的日志写入。判断当前是否有新的日志可读的方法是
       ret = (log->w_off == reader->r_off);
       即判断当前缓冲区的写入位置和当前读进程的读取位置是否相等,如果不相等,则说明有新的日志可读。
       继续向下看,如果有新的日志可读,那么就,首先通过get_entry_len来获取下一条可读的日志记录的长度,从这里可以看出,日志读取进程是以日志记录为单位进行读取的,一次只读取一条记录。get_entry_len的函数实现如下
/* 
 * get_entry_len - Grabs the length of the payload of the next entry starting 
 * from 'off'. 
 * 
 * Caller needs to hold log->mutex. 
 */  
static __u32 get_entry_len(struct logger_log *log, size_t off)  
{  
    __u16 val;  
  
    switch (log->size - off) {  
    case 1:  
        memcpy(&val, log->buffer + off, 1);  
        memcpy(((char *) &val) + 1, log->buffer, 1);  
        break;  
    default:  
        memcpy(&val, log->buffer + off, 2);  
    }  
  
    return sizeof(struct logger_entry) + val;  
}  
 如前文所述,每一条日志记录是由两大部分组成的,一个用于描述这条日志记录的结构体logger_entry,另一个是记录内容本身即有效负载。结构体logger_entry的长度是固定的,只要知道有效负载的长度,就可以知道整条日志记录的长度了而有效负载的长度是记录在结构体logger_entry的成员变量len中,而len成员变量的地址与logger_entry的地址相同,因此,只需要读取记录的开始位置的两个字节就可以了。又由于日志记录缓冲区是循环使用的,这两个节字有可能是第一个字节存放在缓冲区最后一个字节,而第二个字节存放在缓冲区的第一个节,除此之外,这两个字节都是连在一起的。因此,分两种情况来考虑,对于前者,分别通过读取缓冲区最后一个字节和第一个字节来得到日志记录的有效负载长度到本地变量val中,对于后者,直接读取连续两个字节的值到本地变量val中。这两种情况是通过判断日志缓冲区的大小和要读取的日志记录在缓冲区中的位置的差值来区别的,如果相差1,就说明是前一种情况了。最后,把有效负载的长度val加上logger_entry的长度就得到了要读取的日志记录的总长度了
接着往下看,得到了要读取的记录的长度,就调用do_read_log_to_user()函数来执行真正的读取动作
static ssize_t do_read_log_to_user(struct logger_log *log,  
                   struct logger_reader *reader,  
                   char __user *buf,  
                   size_t count)  
{  
    size_t len;  
  
    /* 
     * We read from the log in two disjoint operations. First, we read from 
     * the current read head offset up to 'count' bytes or to the end of 
     * the log, whichever comes first. 
     */  
    len = min(count, log->size - reader->r_off);  
    if (copy_to_user(buf, log->buffer + reader->r_off, len))  
        return -EFAULT;  
  
    /* 
     * Second, we read any remaining bytes, starting back at the head of 
     * the log. 
     */  
    if (count != len)  
        if (copy_to_user(buf + len, log->buffer, count - len))  
            return -EFAULT;  
  
    reader->r_off = logger_offset(reader->r_off + count);  
  
    return count;  
这个函数简单地调用copy_to_user函数来把位于内核空间的日志缓冲区指定的内容拷贝到用户空间的内存缓冲区就可以了,同时,把当前读取日志进程的上下文信息中的读偏移r_off前进到下一条日志记录的开始的位置上。
原创粉丝点击