💎一站式轻松地调用各大LLM模型接口,支持GPT4、智谱、星火、月之暗面及文生图 广告
日志设备的初始化过程是在Logger日志驱动程序的入口函数logger_init中进行的。在分析这个函数之前,我们首先介绍三个结构体变量log_main、log_events和log_radio,它们的类型均为struct logger_log,分别用来保存main、events和radio三种类型的日志记录,如下所示。 **kernel/goldfish/drivers/staging/android/logger.c** ~~~ /* * Defines a log structure with name 'NAME' and a size of 'SIZE' bytes, which * must be a power of two, greater than LOGGER_ENTRY_MAX_LEN, and less than * LONG_MAX minus LOGGER_ENTRY_MAX_LEN. */ #define DEFINE_LOGGER_DEVICE(VAR, NAME, SIZE) \ static unsigned char _buf_ ## VAR[SIZE]; \ static struct logger_log VAR = { \ .buffer = _buf_ ## VAR, \ .misc = { \ .minor = MISC_DYNAMIC_MINOR, \ .name = NAME, \ .fops = &logger_fops, \ .parent = NULL, \ }, \ .wq = __WAIT_QUEUE_HEAD_INITIALIZER(VAR .wq), \ .readers = LIST_HEAD_INIT(VAR .readers), \ .mutex = __MUTEX_INITIALIZER(VAR .mutex), \ .w_off = 0, \ .head = 0, \ .size = SIZE, \ }; DEFINE_LOGGER_DEVICE(log_main, LOGGER_LOG_MAIN, 64*1024) DEFINE_LOGGER_DEVICE(log_events, LOGGER_LOG_EVENTS, 256*1024) DEFINE_LOGGER_DEVICE(log_radio, LOGGER_LOG_RADIO, 64*1024) ~~~ 结构体变量log_main、log_events和log_radio都是通过宏DEFINE_LOGGER_DEVICE来定义的,需要传入的三个参数分别为变量名、设备文件路径,以及要分配的日志缓冲区的大小。前面提到,Logger日志系统将日志记录划分为四种类型。从理论上说,每一种类型的日志记录都应该对应有一个日志缓冲区,即在Logger日志驱动程序中对应有一个logger_log结构体,但是在目前的Logger日志驱动程序实现中,只定义了三个日志缓冲区,其中,类型为system和main的日志记录保存在同一个日志缓冲区log_main中,而类型为events和radio的日志记录分别保存在日志缓冲区log_events和log_radio中。 从宏DEFINE_LOGGER_DEVICE的定义可以看出,每一种类型的日志缓冲区都是将各自的日志记录保存在一个静态分配的unsigned char数组中,数组的大小由参数SIZE决定,其中,类型为main和radio的日志缓冲区的大小为64K,而类型为events的日志缓冲区的大小为256K。 每一个日志缓冲区都对应有一个misc类型的日志设备,以便运行在用户空间的程序可以访问它们。每一个日志设备都对应有一个文件名,它是由参数NAME指定的。从结构体变量log_main、log_events和log_radio的定义可以看出,类型为main、radio和events的日志缓冲区对应的日志设备文件分别为LOGGER_LOG_MAIN、LOGGER_LOG_RADIO和LOGGER_LOG_EVENTS。 **kernel/goldfish/drivers/staging/android/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日志驱动程序初始化完成之后,我们就可以在/dev/log目录下看到三个日志设备文件main、events和radio。这三个日志设备对应的文件操作函数表均为logger_fops,它的定义如下所示。 **kernel/goldfish/drivers/staging/android/logger.c** ~~~ static struct file_operations logger_fops = { .owner = THIS_MODULE, .read = logger_read, .aio_write = logger_aio_write, .poll = logger_poll, .unlocked_ioctl = logger_ioctl, .compat_ioctl = logger_ioctl, .open = logger_open, .release = logger_release, }; ~~~ 文件操作函数表logger_fops指定的日志设备文件操作函数比较多,但是我们只关注日志设备打开、读取和写入函数open、read和aio_write的定义,它们分别被设置为logger_open、logger_read和logger_aio_write函数。 每一个硬件设备都有自己的主设备号和从设备号。由于日志设备的类型为misc,并且同一类型的设备都具有相同的主设备号,因此,在定义这些日志设备时,就不需要指定它们的主设备号了,等到注册这些日志设备时,再统一指定。从宏DEFINE_LOGGER_DEVICE的定义可以看出,这些日志设备的从设备号被设置为MISC_DYNAMIC_MINOR,表示由系统动态分配,它的定义如下所示。 **kernel/goldfish/drivers/staging/android/logger.h** ~~~ #define MISC_DYNAMIC_MINOR 255 ~~~ 一般来说,我们在开发驱动程序时,都不应该去静态指定设备的从设备号,因为静态指定的从设备号会比较容易发生冲突,而动态分配的从设备号能够保证唯一性。 结构体变量log_main、log_events和log_radio的其余成员变量的初始化过程都比较简单,这里就不详细介绍了。例如,它们的成员变量wq、reader和mutex分别使用宏__WAIT_QUEUE_HEAD_INITIALIZER、LIST_HEAD_INIT和__MUTEX_INITIALIZER来初始化,因为它们的类型分别为等待队列、队列和互斥量。 我们就从函数logger_init开始,分析日志设备的初始化过程。 **kernel/goldfish/drivers/staging/android/logger.c** ~~~ static int __init logger_init(void) { int ret; ret = init_log(&log_main); if (unlikely(ret)) goto out; ret = init_log(&log_events); if (unlikely(ret)) goto out; ret = init_log(&log_radio); if (unlikely(ret)) goto out; out: return ret; } ~~~ 日志设备的初始化过程实际上就是将三个日志设备注册到系统中,这是分别通过调用函数init_log来实现的。 **kernel/goldfish/drivers/staging/android/logger.c** ~~~ static int __init init_log(struct logger_log *log) { int ret; ret = misc_register(&log->misc); if (unlikely(ret)) { printk(KERN_ERR "logger: failed to register misc " "device for log '%s'!\n", log->misc.name); return ret; } printk(KERN_INFO "logger: created %luK log '%s'\n", (unsigned long) log->size >> 10, log->misc.name); return 0; } ~~~ 在init_log函数中,主要是通过调用misc_register函数将相应的日志设备注册到系统中。 **kernel/goldfish/drivers/char/misc.c** ~~~ int misc_register(struct miscdevice * misc) { struct miscdevice *c; dev_t dev; int err = 0; INIT_LIST_HEAD(&misc->list); mutex_lock(&misc_mtx); list_for_each_entry(c, &misc_list, list) { if (c->minor == misc->minor) { mutex_unlock(&misc_mtx); return -EBUSY; } } if (misc->minor == MISC_DYNAMIC_MINOR) { int i = DYNAMIC_MINORS; while (--i >= 0) if ( (misc_minors[i >> 3] & (1 << (i&7))) == 0) break; if (i<0) { mutex_unlock(&misc_mtx); return -EBUSY; } misc->minor = i; } if (misc->minor < DYNAMIC_MINORS) misc_minors[misc->minor >> 3] |= 1 << (misc->minor & 7); dev = MKDEV(MISC_MAJOR, misc->minor); misc->this_device = device_create(misc_class, misc->parent, dev, NULL, "%s", misc->name); if (IS_ERR(misc->this_device)) { err = PTR_ERR(misc->this_device); goto out; } /* * Add it to the front, so that later devices can "override" * earlier defaults */ list_add(&misc->list, &misc_list); out: mutex_unlock(&misc_mtx); return err; } ~~~ 系统中所有的misc设备都保存在一个misc_list列表中。函数第10行到第15行检查所要注册的misc设备的从设备号是否已经被注册。如果是的话,注册就失败了,因为同一类型设备的从设备号是不能相同的。由于Logger日志驱动程序已经指定它所要注册的日志设备的从设备号是动态分配的,即指定为MISC_DYNAMIC_MINOR,因此,这一步检查就通过了。 函数第17行到第27行为所要注册的日志设备分配从设备号。系统可分配的misc从设备号一共有64个,即从0到63。这些从设备号的使用情况记录在数组misc_minors中,它的定义如下所示。 **kernel/goldfish/drivers/char/misc.c** ~~~ #define DYNAMIC_MINORS 64 /* like dynamic majors */ static unsigned char misc_minors[DYNAMIC_MINORS / 8]; ~~~ 数组misc_minors的类型为unsigned char,因此,每一个元素可以管理8个从设备号,64个从设备号就需要8个元素,即数组misc_minors的大小为8。如果某一个从设备号已经被分配了,那么它在数组misc_minors中所对应的位就等于1,否则就等于0。如何在数组misc_minors中找到从设备号i所对应的位呢?首先找到它对应的数组元素位置,即i >> 3,然后取出从设备号i的低三位,即i & 7,那么从设备号i在数组misc_minors中对应的位便是第i >> 3个元素的第i & 7位了。因此,第20行只要检查这一位是否等于0,就可以知道从设备号i是否已经被分配了。如果没有被分配,那么第26行就将它分配给当前正在注册的日志设备,接着第30行在数组misc_minors中记录该从设备号已经被分配。 函数第31行使用misc主设备号MISC_MAJOR和前面得到的从设备号来创建一个设备号对象dev,接着第33行以它为参数调用函数device_create将日志设备misc注册到系统中。MISC_MAJOR是一个宏,它的定义如下所示。 **kernel/goldfish/include/linux/major.h** ~~~ #define MISC_MAJOR 10 ~~~ 日志设备注册成功之后,函数第44行就将它添加到misc设备列表misc_list中,表示日志设备注册成功了。 第33行调用device_create函数注册日志设备时,最后一个参数用来指定日志设备的名称,即它在设备系统目录/dev中对应的文件名称。从前面的调用过程可以知道,我们要注册的三个日志设备的名称分别为“log_main”、“log_events”和“log_radio”,因此,应当在设备上的/dev目录中看到log_main、log_events和log_radio三个文件。然而,在本章的开头提到,这三个日志设备对应的文件分别为/dev/log/main、/dev/log/events和/dev/log/radio,这是为什么呢? 在前面的2.3.4小节中提到,Android系统使用一种uevent机制来管理系统的设备文件。当Logger日志驱动程序注册一个日志设备时,内核就会发出一个uevent事件,这个uevent最终由init进程中的handle_device_event函数来处理,它的实现如下所示。 **system/core/init/devices.c** ~~~ static void handle_device_event(struct uevent *uevent) { char devpath[96]; int devpath_ready = 0; char *base, *name; char **links = NULL; int block; int i; ...... name = strrchr(uevent->path, '/'); ...... name++; ...... if(!strncmp(uevent->subsystem, "block", 5)) { ...... } else { ...... if (!strncmp(uevent->subsystem, "usb", 3)) { ...... } else if (!strncmp(uevent->subsystem, "graphics", 8)) { ...... } else if (!strncmp(uevent->subsystem, "oncrpc", 6)) { ...... } else if (!strncmp(uevent->subsystem, "adsp", 4)) { ...... } else if (!strncmp(uevent->subsystem, "msm_camera", 10)) { ...... } else if(!strncmp(uevent->subsystem, "input", 5)) { ...... } else if(!strncmp(uevent->subsystem, "mtd", 3)) { ...... } else if(!strncmp(uevent->subsystem, "sound", 5)) { ...... } else if(!strncmp(uevent->subsystem, "misc", 4) && !strncmp(name, "log_", 4)) { base = "/dev/log/"; mkdir(base, 0755); name += 4; } else ...... } if (!devpath_ready) snprintf(devpath, sizeof(devpath), "%s%s", base, name); if(!strcmp(uevent->action, "add")) { make_device(devpath, uevent->path, block, uevent->major, uevent->minor); if (links) { for (i = 0; links[i]; i++) make_link(devpath, links[i]); } } ...... } ~~~ 函数handle_device_event如果发现新注册的设备类型为misc,并且设备名称是以“log_”开头的,它便会在/dev目录中创建一个log目录,接着将设备名称的前四个字符去掉,即去掉前面的“log_”子字符串,最后就使用新的设备名称在/dev/log目录下创建一个设备文件。因此,我们就不会在设备上的/dev目录中看到log_main、log_events和log_radio三个设备文件,而是在/dev/log目录中看到main、events和radio三个设备文件。