Android日志系统探究

2024-06-09 01:48
文章标签 android 日志 探究 系统

本文主要是介绍Android日志系统探究,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

        Android的日志系统是Kernel层实现了若干个环形Buffer实现的。系统各个日志读写操作都是针对这几个RingBuffer来实现的。那就来一窥Kernel是怎么做的。相关源码是位于driver/staging/android/下面的logger.c和logger.h两个文件

1,在整个Android日志系统的位置
图片搜索结果
2,在logger.c中,入口函数
   
  1. static int __init logger_init(void)
  2. {
  3. ...
  4. ret = create_log(LOGGER_LOG_MAIN, 256*1024);
  5. ret = create_log(LOGGER_LOG_EVENTS, 256*1024);
  6. ret = create_log(LOGGER_LOG_RADIO, 256*1024);
  7. ret = create_log(LOGGER_LOG_SYSTEM, 256*1024);
  8. ...
  9. }
分别来看create_log的实现和LOGGER_LOG_*的相关定义
    
  1. static int __init create_log(char *log_name, int size)
  2. {
  3. ...
  4. log->buffer = buffer;
  5. /* finally, initialize the misc device for this log */
  6. ret = misc_register(&log->misc);
  7. ...
  8. }
     
  1. #define LOGGER_LOG_RADIO "log_radio" /* radio-related messages */
  2. #define LOGGER_LOG_EVENTS "log_events" /* system/hardware events */
  3. #define LOGGER_LOG_SYSTEM "log_system" /* system/framework messages */
  4. #define LOGGER_LOG_MAIN "log_main" /* everything else */
  5. ...
从上可以知道,create_log函数就是把创建4个misc设备,并注册上。在创建的过程中会初始化各个设备的logger_log结构体。也就是说有4个logger_log结构体的实例。这4个MISC设备(即对应4种Logger)的Buffer大小为256Kb,即RING BUFFER的大小为256Kb。
再来看这个十分重要的结构体logger_log的其它成员
    
  1. /**
  2. * struct logger_log - represents a specific log, such as 'main' or 'radio'
  3. * @buffer: The actual ring buffer
  4. * @misc: The "misc" device representing the log
  5. * @wq: The wait queue for @readers
  6. * @readers: This log's readers
  7. * @mutex: The mutex that protects the @buffer
  8. * @w_off: The current write head offset
  9. * @head: The head, or location that readers start reading at.
  10. * @size: The size of the log
  11. * @logs: The list of log channels
  12. *
  13. * This structure lives from module insertion until module removal, so it does
  14. * not need additional reference counting. The structure is protected by the
  15. * mutex 'mutex'.
  16. */
  17. struct logger_log {
  18. unsigned char *buffer;
  19. struct miscdevice misc;
  20. wait_queue_head_t wq;
  21. struct list_head readers;
  22. struct mutex mutex;
  23. size_t w_off;
  24. size_t head;
  25. size_t size;
  26. struct list_head logs;
  27. };
不复杂,注释的解析也十分清楚。
用户层对于这4个MISC设备的操作,是通过/dev/log/*下面的几个设备实现的,它同样也有file_operations结构体中的读写函数指针,如下:
    
  1. static const 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. };
3,MISC设备节点是怎么生成?
在运行的系统的/dev/log目录下有如下节点,
    
  1. root@U:/dev/log # ls
  2. events
  3. main
  4. radio
  5. system
这是因为在上面提到的misc_register(&log->misc)函数,在前面的Kernel设备驱动相关文章可以知道,最终是调用device_create() -> device_add()->kobject_uevent(&dev->kobj, KOBJ_ADD);就会发送一个KOBJ_ADD的UEVENT给到上层,由《Android的根文件系统》可以知道,这是由init进程来处理的。在init项目中devices.cpp会来处理这个ADD事件
    
  1. ...  
  2. } else if(!strncmp(uevent->subsystem, "misc", 4) &&
  3. !strncmp(name, "log_", 4)) {
  4. INFO("kernel logger is deprecatedn");
  5. base = "/dev/log/";
  6. make_dir(base, 0755);
  7. name += 4;
  8. ...
如果设备的名字前缀是log_的话,就会创建/dev/log/目录。所以就出现dev/log/下面的4个文件。
4,写操作分析
写操作是写一条消息,封装在logger_entry为头的消息。一次写操作大概构成如下:
struct logger_entry | priority | tag | msg
比如logcat打印出来如下:
12-06 16:12:23.849  3700  3788  | D  | WifiStateMachine  | : handleMessage: X
logger_aio_write()函数如下:
    
  1. static ssize_t logger_aio_write(struct kiocb *iocb, const struct iovec *iov,
  2. unsigned long nr_segs, loff_t ppos)
  3. {
  4. //1,填充header头的结构体
  5. header.pid = current->tgid;
  6. header.tid = current->pid;
  7. header.sec = now.tv_sec;
  8. header.nsec = now.tv_nsec;
  9. header.euid = current_euid();
  10. header.len = min_t(size_t, iocb->ki_nbytes, LOGGER_ENTRY_MAX_PAYLOAD);
  11. header.hdr_size = sizeof(struct logger_entry);
  12. /* null writes succeed, return zero */
  13. if (unlikely(!header.len))
  14. return 0;
  15. //如果写的一条信息,会覆盖正在进程读的信息,那么就丢弃这条读,把读的偏移指到下一条安全的信息。
  16. fix_up_readers(log, sizeof(struct logger_entry) + header.len);
  17. //2,把header信息定写入
  18. do_write_log(log, &header, sizeof(struct logger_entry));
  19. //3,把用户传过来的Message信息写入
  20. while (nr_segs-- > 0) {
  21. size_t len;
  22. ssize_t nr;
  23. /* figure out how much of this vector we can keep */
  24.     //这里计算剩余的信息是否超过了MAX_PAYLOAD的长度,即4K,超过部分将会丢弃。
  25.     //一条信息基本上也不会超过4K,基本是取实际信息长度
  26. len = min_t(size_t, iov->iov_len, header.len - ret);
  27. /* write out this segment's payload */
  28. nr = do_write_log_from_user(log, iov->iov_base, len);
  29. if (unlikely(nr < 0)) {
  30. log->w_off = orig;
  31. mutex_unlock(&log->mutex);
  32. return nr;
  33. }
  34. iov++;
  35. ret += nr;
  36. }
  37. //4,唤醒读的队列
  38. /* wake up any blocked readers */
  39. wake_up_interruptible(&log->wq);
  40. return ret;
  41. }
一条log日志的头部信息( 12-06 16:12:23.849  3700  3788 )是在kernel中写入的。do_write_log()
    
  1. static void do_write_log(struct logger_log *log, const void *buf, size_t count)
  2. {
  3. size_t len;
  4. //Buffer剩余的长度和消息长度取较小的值
  5. //如果buffer剩余长度不够写完一条信息,就进入下面的if,把剩余的部分,从头部开始写进去。
  6. len = min(count, log->size - log->w_off);
  7. memcpy(log->buffer + log->w_off, buf, len);
  8. //如果一条Log,不够剩余的buffer的话,上面memcpy就是写了前半部分,
  9. //下面的memcpy就把剩下的后半部分(cout-len),写到log->buffer的头。
  10. //这里体现了循环Buffer
  11. if (count != len)
  12. memcpy(log->buffer, buf + len, count - len);
  13. //移动w_off
  14. log->w_off = logger_offset(log, log->w_off + count);
  15. }
写完头部,再写用户层传过来的信息
    
  1. static ssize_t do_write_log_from_user(struct logger_log *log,
  2. const void __user *buf, size_t count)
  3. {
  4. //同理会作判断,是否剩余buffer能够写完,否则就把剩余的部分从头开始写
  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. log->w_off = logger_offset(log, log->w_off + count);
  11. return count;
  12. }
5,写操作基本上完成了,接下来看读操作。
    
  1. static ssize_t logger_read(struct file *file, char __user *buf,
  2. size_t count, loff_t *pos)
  3. {
  4. //1,如果读的偏移r_off与写的偏移w_off相等,则循环阻塞,直到write最后唤醒
  5. while (1) {
  6. prepare_to_wait(&log->wq, &wait, TASK_INTERRUPTIBLE);//write函数的wake_up_interruptible(&log->wq);会唤醒
  7. //Write的off存在Logger_log即内存buffer中,而r_off存在reder,即读的进程中。所以执行两次不同的logcat,都是从头开始读的。
  8.         ret = (log->w_off == reader->r_off);
  9. mutex_unlock(&log->mutex);
  10. if (!ret)
  11. break;//如果不相等就跳出去处理。
  12. schedule(); //把cpu调试出去,等待队列的唤醒
  13. }
  14. if (!reader->r_all)
  15. reader->r_off = get_next_entry_by_uid(log,
  16. reader->r_off, current_euid());
  17. /* get the size of the next entry */
  18. //2,获取要读的整个一条日志的长度,同样由两部分组成,一个head长度+信息的长度。
  19.     ret = get_user_hdr_len(reader->r_ver) +
  20. get_entry_msg_len(log, reader->r_off);
  21. /* get exactly one entry from the log */
  22.     //3,把log读ret长度的信息到buf中。
  23. ret = do_read_log_to_user(log, reader, buf, ret);
  24. out:
  25. mutex_unlock(&log->mutex);
  26. return ret;
  27. }
来看看第3步do_read_log_to_user的实现
    
  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.     //先把header copy到用户空间
  7. entry = get_entry_header(log, reader->r_off, &scratch);
  8. if (copy_header_to_user(reader->r_ver, entry, buf))
  9. return -EFAULT;
  10. count -= get_user_hdr_len(reader->r_ver);
  11. buf += get_user_hdr_len(reader->r_ver);
  12. msg_start = logger_offset(log,
  13. reader->r_off + sizeof(struct logger_entry));
  14. /*
  15. * We read from the msg in two disjoint operations. First, we read from
  16. * the current msg head offset up to 'count' bytes or to the end of
  17. * the log, whichever comes first.
  18. */
  19. //同样做是否到log buffer尾部的判断,否则就要读两次
  20. len = min(count, log->size - msg_start);
  21. if (copy_to_user(buf, log->buffer + msg_start, len))
  22. return -EFAULT;
  23. ...
  24. if (count != len)
  25. if (copy_to_user(buf + len, log->buffer, count - len))
  26. return -EFAULT;
  27. //移动reader的off偏移
  28. reader->r_off = logger_offset(log, reader->r_off +
  29. sizeof(struct logger_entry) + count);
  30. return count + get_user_hdr_len(reader->r_ver);
  31. }
这样Logger的实现基本就完成了,循环Buffer的实现,也就是在读与写的操作过程,是否已经到了buffer尾的判断。
    
  1. len = min(count, log->size - msg_start);
  2. if (copy_to_user(buf, log->buffer + msg_start, len))
  3. if (count != len)
  4. if (copy_to_user(buf + len, log->buffer, count - len))
  5. return -EFAULT;
RingBuffer是Kernel一个常用的数据结构,从Logger系统中看它的实现,看到其它模块使用,心里也有谱了。

这篇关于Android日志系统探究的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/1043922

相关文章

通信系统网络架构_2.广域网网络架构

1.概述          通俗来讲,广域网是将分布于相比局域网络更广区域的计算机设备联接起来的网络。广域网由通信子网于资源子网组成。通信子网可以利用公用分组交换网、卫星通信网和无线分组交换网构建,将分布在不同地区的局域网或计算机系统互连起来,实现资源子网的共享。 2.网络组成          广域网属于多级网络,通常由骨干网、分布网、接入网组成。在网络规模较小时,可仅由骨干网和接入网组成

Eclipse+ADT与Android Studio开发的区别

下文的EA指Eclipse+ADT,AS就是指Android Studio。 就编写界面布局来说AS可以边开发边预览(所见即所得,以及多个屏幕预览),这个优势比较大。AS运行时占的内存比EA的要小。AS创建项目时要创建gradle项目框架,so,创建项目时AS比较慢。android studio基于gradle构建项目,你无法同时集中管理和维护多个项目的源码,而eclipse ADT可以同时打开

android 免费短信验证功能

没有太复杂的使用的话,功能实现比较简单粗暴。 在www.mob.com网站中可以申请使用免费短信验证功能。 步骤: 1.注册登录。 2.选择“短信验证码SDK” 3.下载对应的sdk包,我这是选studio的。 4.从头像那进入后台并创建短信验证应用,获取到key跟secret 5.根据技术文档操作(initSDK方法写在setContentView上面) 6.关键:在有用到的Mo

android一键分享功能部分实现

为什么叫做部分实现呢,其实是我只实现一部分的分享。如新浪微博,那还有没去实现的是微信分享。还有一部分奇怪的问题:我QQ分享跟QQ空间的分享功能,我都没配置key那些都是原本集成就有的key也可以实现分享,谁清楚的麻烦详解下。 实现分享功能我们可以去www.mob.com这个网站集成。免费的,而且还有短信验证功能。等这分享研究完后就研究下短信验证功能。 开始实现步骤(新浪分享,以下是本人自己实现

Android我的二维码扫描功能发展史(完整)

最近在研究下二维码扫描功能,跟据从网上查阅的资料到自己勉强已实现扫描功能来一一介绍我的二维码扫描功能实现的发展历程: 首页通过网络搜索发现做android二维码扫描功能看去都是基于google的ZXing项目开发。 2、搜索怎么使用ZXing实现自己的二维码扫描:从网上下载ZXing-2.2.zip以及core-2.2-source.jar文件,分别解压两个文件。然后把.jar解压出来的整个c

android 带与不带logo的二维码生成

该代码基于ZXing项目,这个网上能下载得到。 定义的控件以及属性: public static final int SCAN_CODE = 1;private ImageView iv;private EditText et;private Button qr_btn,add_logo;private Bitmap logo,bitmap,bmp; //logo图标private st

Android多线程下载见解

通过for循环开启N个线程,这是多线程,但每次循环都new一个线程肯定很耗内存的。那可以改用线程池来。 就以我个人对多线程下载的理解是开启一个线程后: 1.通过HttpUrlConnection对象获取要下载文件的总长度 2.通过RandomAccessFile流对象在本地创建一个跟远程文件长度一样大小的空文件。 3.通过文件总长度/线程个数=得到每个线程大概要下载的量(线程块大小)。

Linux系统稳定性的奥秘:探究其背后的机制与哲学

在计算机操作系统的世界里,Linux以其卓越的稳定性和可靠性著称,成为服务器、嵌入式系统乃至个人电脑用户的首选。那么,是什么造就了Linux如此之高的稳定性呢?本文将深入解析Linux系统稳定性的几个关键因素,揭示其背后的技术哲学与实践。 1. 开源协作的力量Linux是一个开源项目,意味着任何人都可以查看、修改和贡献其源代码。这种开放性吸引了全球成千上万的开发者参与到内核的维护与优化中,形成了

时间服务器中,适用于国内的 NTP 服务器地址,可用于时间同步或 Android 加速 GPS 定位

NTP 是什么?   NTP 是网络时间协议(Network Time Protocol),它用来同步网络设备【如计算机、手机】的时间的协议。 NTP 实现什么目的?   目的很简单,就是为了提供准确时间。因为我们的手表、设备等,经常会时间跑着跑着就有误差,或快或慢的少几秒,时间长了甚至误差过分钟。 NTP 服务器列表 最常见、熟知的就是 www.pool.ntp.org/zo

高仿精仿愤怒的小鸟android版游戏源码

这是一款很完美的高仿精仿愤怒的小鸟android版游戏源码,大家可以研究一下吧、 为了报复偷走鸟蛋的肥猪们,鸟儿以自己的身体为武器,仿佛炮弹一样去攻击肥猪们的堡垒。游戏是十分卡通的2D画面,看着愤怒的红色小鸟,奋不顾身的往绿色的肥猪的堡垒砸去,那种奇妙的感觉还真是令人感到很欢乐。而游戏的配乐同样充满了欢乐的感觉,轻松的节奏,欢快的风格。 源码下载