***************************************************************************************************************************
作者:EasyWave 时间:2012.07.29
类别:Android系统源码分析 声明:转载,请保留链接
注意:如有错误,欢迎指正。这些是我学习的日志文章......
***************************************************************************************************************************
在我的博文基于goldfish和android2.3.5学习之:开天辟地Android启动机制[一]中,介绍了整个android系统的启动机制,这次将更深入的详细分析android的uevent的机制以及如何android是如何透过内核传递过来的数据通过uevent建立设备节点以及一些hotplug事件。在Andorid2.3.5源码system/core/init.c函数中ueventd_main()函数。详细的代码如下:
if (!strcmp(basename(argv[0]), "ueventd")) //得到运行程序ueventd.rc的全路径名下的ueventd
return ueventd_main(argc, argv); //如果可以找到ueventd.rc,则执行ueventd_main函数
这里有个函数不得不说,这个函数就是basename函数,这个函数的主要作用就是得到ueventd.rc文件所在路径下的文件名,即ueventd。这个函数的具体的代码如下:
#include <sys/cdefs.h>
#include <errno.h>
#include <libgen.h>
#include <stdlib.h>
#include <string.h>
#include <sys/param.h>
char* basename(const char* path)
{
static char* bname = NULL;
int ret;
if (bname == NULL) {
bname = (char *)malloc(MAXPATHLEN);
if (bname == NULL)
return(NULL);
}
ret = basename_r(path, bname, MAXPATHLEN);
return (ret < 0) ? NULL : bname;
}
上面这个最终是调用basename_r函数来解析
ueventd.rc所在的路径下的ueventd的文件名。这个函数式在bionic/libc/bionic/basename.c中。具体的实现函数却是在basename_r函数中,这个函数也是在bionic/libc/bionic/basename_r.c中来实现的,具体的代码如下:
#include <libgen.h>
#include <errno.h>
#include <string.h>
#include <sys/param.h>
int basename_r(const char* path, char* buffer, size_t bufflen)//返回路径中文件名的安全函数
{
const char *endp, *startp;
int len, result;
char temp[2];
/* Empty or NULL string gets treated as "." */
if (path == NULL || *path == '\0') {
startp = ".";
len = 1;
goto Exit;
}
/* Strip trailing slashes */
endp = path + strlen(path) - 1;
while (endp > path && *endp == '/')
endp--;
/* All slashes becomes "/" */
if (endp == path && *endp == '/') {
startp = "/";
len = 1;
goto Exit;
}
/* Find the start of the base */
startp = endp;
while (startp > path && *(startp - 1) != '/')
startp--;
len = endp - startp +1;
Exit:
result = len;
if (buffer == NULL) {
return result;
}
if (len > (int)bufflen-1) {
len = (int)bufflen-1;
result = -1;
errno = ERANGE;
}
if (len >= 0) {
memcpy( buffer, startp, len );
buffer[len] = 0;
}
return result;
}
透过上面的basename_r函数我们可以看出来主要是将目录路径"/"一个一个找出来并且丢弃,最终找到ueventd与strcmp(basename(argv[0]), "ueventd")进行比较,如果找到文件,则进入ueventd_main(argc, argv);进行uevent事件的主循环的处理中。在android2.3.5源码中system/core/rootdir/中ueventd.rc文件如下:
/dev/null 0666 root root
/dev/zero 0666 root root
/dev/full 0666 root root
/dev/ptmx 0666 root root
/dev/tty 0666 root root
/dev/random 0666 root root
/dev/urandom 0666 root root
/dev/ashmem 0666 root root
/dev/binder 0666 root root
# logger should be world writable (for logging) but not readable
/dev/log/* 0662 root log
# the msm hw3d client device node is world writable/readable.
/dev/msm_hw3dc 0666 root root
# gpu driver for adreno200 is globally accessible
/dev/kgsl 0666 root root
# these should not be world writable
/dev/diag 0660 radio radio
/dev/diag_arm9 0660 radio radio
/dev/android_adb 0660 adb adb
/dev/android_adb_enable 0660 adb adb
/dev/ttyMSM0 0600 bluetooth bluetooth
/dev/ttyHS0 0600 bluetooth bluetooth
/dev/uinput 0660 system bluetooth
/dev/alarm 0664 system radio
/dev/tty0 0660 root system
/dev/graphics/* 0660 root graphics
/dev/msm_hw3dm 0660 system graphics
/dev/input/* 0660 root input
/dev/eac 0660 root audio
/dev/cam 0660 root camera
/dev/pmem 0660 system graphics
/dev/pmem_adsp* 0660 system audio
/dev/pmem_camera* 0660 system camera
/dev/oncrpc/* 0660 root system
/dev/adsp/* 0660 system audio
/dev/snd/* 0660 system audio
/dev/mt9t013 0660 system system
/dev/msm_camera/* 0660 system system
/dev/akm8976_daemon 0640 compass system
/dev/akm8976_aot 0640 compass system
/dev/akm8973_daemon 0640 compass system
/dev/akm8973_aot 0640 compass system
/dev/bma150 0640 compass system
/dev/cm3602 0640 compass system
/dev/akm8976_pffd 0640 compass system
/dev/lightsensor 0640 system system
/dev/msm_pcm_out* 0660 system audio
/dev/msm_pcm_in* 0660 system audio
/dev/msm_pcm_ctl* 0660 system audio
/dev/msm_snd* 0660 system audio
/dev/msm_mp3* 0660 system audio
/dev/audience_a1026* 0660 system audio
/dev/tpa2018d1* 0660 system audio
/dev/msm_audpre 0660 system audio
/dev/msm_audio_ctl 0660 system audio
/dev/htc-acoustic 0660 system audio
/dev/vdec 0660 system audio
/dev/q6venc 0660 system audio
/dev/snd/dsp 0660 system audio
/dev/snd/dsp1 0660 system audio
/dev/snd/mixer 0660 system audio
/dev/smd0 0640 radio radio
/dev/qemu_trace 0666 system system
/dev/qmi 0640 radio radio
/dev/qmi0 0640 radio radio
/dev/qmi1 0640 radio radio
/dev/qmi2 0640 radio radio
/dev/bus/usb/* 0660 root usb
/dev/usb_accessory 0660 root usb
# CDMA radio interface MUX
/dev/ts0710mux* 0640 radio radio
/dev/ppp 0660 radio vpn
/dev/tun 0640 vpn vpn
# sysfs properties
/sys/devices/virtual/input/input* enable 0660 root input
/sys/devices/virtual/input/input* poll_delay 0660 root input
/sys/devices/virtual/usb_composite/* enable 0664 root system
而ueventd_main的代码见下面,现在更深入的来分析下这个函数。这个函数式在system/core/init/ueventd.c
int ueventd_main(int argc, char **argv)
{
struct pollfd ufd;
int nr;
char tmp[32];
open_devnull_stdio(); //打开/dev/__null,并且重定向stdin、stdout、stderr
log_init(); //打开log文件
INFO("starting ueventd\n");
get_hardware_name(hardware, &revision); //透过/proc/cpuinfo得到当前项目的hardware以及revision
ueventd_parse_config_file("/ueventd.rc"); //解析ueventd.rc文件
snprintf(tmp, sizeof(tmp), "/ueventd.%s.rc", hardware);
ueventd_parse_config_file(tmp); //解析ueventd.xxxxxx.rc文件,比如:goldfish为ueventd.goldfish.rc文件
device_init();
ufd.events = POLLIN;
ufd.fd = get_device_fd();
while(1) {
ufd.revents = 0;
nr = poll(&ufd, 1, -1);
if (nr <= 0)
continue;
if (ufd.revents == POLLIN)
handle_device_fd();
}
}
首先来分析下open_devnull_stdio();这个函数,这个主要是打开/dev/__null__,同时将stdin,stdout,stderr进行重定向,具体的代码如下:
void open_devnull_stdio(void)
{
int fd;
static const char *name = "/dev/__null__";
if (mknod(name, S_IFCHR | 0600, (1 << 8) | 3) == 0) { //设置节点属性,S_IFCHR:也就是说这是一个字符设备。0600是文件的属性。
fd = open(name, O_RDWR); //打开/dev/__null__设备
unlink(name); //删除一个文件的目录项并减少它的链接数
if (fd >= 0) { //打开成功
dup2(fd, 0); //重定向stdin
dup2(fd, 1); //重定向stdout
dup2(fd, 2); //重定向stderr
if (fd > 2) { //关闭文件并且返回成功
close(fd);
}
return;
}
}
//否则返回失败
exit(1);
}
而log_init();主要是建立一个msg的log文件以记录上面的操作的记录文件。其函数代码如下[这个函数就不深究了,很容易看得懂。相信大家都应该还记得这个命令:cat /proc/kmsg,你自己可以在开发板上打下这个命令,看看会出现什么信息出来。。代码如下:
void log_init(void)
{
static const char *name = "/dev/__kmsg__";
if (mknod(name, S_IFCHR | 0600, (1 << 8) | 11) == 0) {
log_fd = open(name, O_WRONLY);
fcntl(log_fd, F_SETFD, FD_CLOEXEC); //给文件强制加锁,设置文件为FD_CLOEXEC,这标志大家可以到网络搜索具体的意思,主要跟exec()函数有关的。
unlink(name);
}
}
同时这个函数需要说下,这个函数就是get_hardware_name(hardware, &revision);这个是透过/proc/cpuinfo得到当前硬件的名称和版本,相信做linux和android内核移植和驱动开发的,应该都知道这个命令:cat /proc/cpuinfo 吧,这个可以在自己的开发板上去测试下,看看会出现什么内容。现在重点分析ueventd_parse_config_file("/ueventd.rc");这个函数。这个函数式ueventd的重点之重,函数代码如下:
int ueventd_parse_config_file(const char *fn)
{
char *data;
data = read_file(fn, 0);
if (!data) return -1;
parse_config(fn, data);
DUMP();
return 0;
}
这里面的read_file(fn,0)主要是读取ueventd.rc的文件大小,并且存储在内存中。而parse_config(fn, data);这个才是重点,这是解析ueventd.rc文件的,这个函数是在system/core/init/ueventd_parser.c中,现在来深入的分析这个函数。
static void parse_config(const char *fn, char *s)
{
struct parse_state state;
char *args[UEVENTD_PARSER_MAXARGS];
int nargs;
nargs = 0;
state.filename = fn;
state.line = 1;
state.ptr = s;
state.nexttoken = 0;
state.parse_line = parse_line_device; //具体的解析device,这是一个回调函数
for (;;) {
int token = next_token(&state); //循环查找ueventd.rc中的/dev/null, /dev/zero等
switch (token) {
case T_EOF:
state.parse_line(&state, 0, 0); //到了ueventd.rc的末尾的话,做最后的解析,并直接返回
return;
case T_NEWLINE: //如果到了换了一行的时候,去做ueventd.rc的解析工作
if (nargs) {
state.parse_line(&state, nargs, args);
nargs = 0;
}
break;
case T_TEXT: //如果正在解析本行的数据,详细见ueventd.rc文件
if (nargs < UEVENTD_PARSER_MAXARGS) {
args[nargs++] = state.text;
}
break;
}
}
}
这个函数在/dev/的都是只有四个可选参数,而只有在/sys/下的时候多了一个attr的选项,也就是5个选项。具体的解析在
parse_line_device函数中的set_device_permission()有详细的处理。具体的代码如下:
void set_device_permission(int nargs, char **args)
{
char *name;
char *attr = 0;
mode_t perm;
uid_t uid;
gid_t gid;
int prefix = 0;
char *endptr;
int ret;
char *tmp = 0;
if (nargs == 0)
return;
if (args[0][0] == '#')
return;
name = args[0]; //就拿ueventd.rc文件中/dev/null 0666 root root 和 /sys/devices/virtual/input/input* enable 0660 root input 来说吧
if (!strncmp(name,"/sys/", 5) && (nargs == 5)) { //查找/sys/的设备,一般都是有5个参数的。
INFO("/sys/ rule %s %s\n",args[0],args[1]);
attr = args[1]; //因此将args[1]赋值给attr
args++;
nargs--;
}
if (nargs != 4) { //如果参数不对,则直接退出,不解析了,因为出错了。
ERROR("invalid line ueventd.rc line for '%s'\n", args[0]);
return;
}
/* If path starts with mtd@ lookup the mount number. */
if (!strncmp(name, "mtd@", 4)) { //查找是否有mtd分区,如果有这mount mtd分区
int n = mtd_name_to_number(name + 4);
if (n >= 0)
asprintf(&tmp, "/dev/mtd/mtd%d", n);
name = tmp;
} else { //否则就做常规的设备解析
int len = strlen(name);
if (name[len - 1] == '*') {
prefix = 1;
// 如果设备中'*'字符,这告诉add_dev_perms有前缀符号,比如:/dev/input/* 也就是这个有可能会有0,1,2,3等多个。
name[len - 1] = '\0';
}
}
perm = strtol(args[1], &endptr, 8); //将args[1]转换为8进制,比如字符0666转换为真正的整形。
if (!endptr || *endptr != '\0') {
ERROR("invalid mode '%s'\n", args[1]);
free(tmp);
return;
}
ret = get_android_id(args[2]); //得到android_id定义的name,具体见:android_ids表格。
if (ret < 0) {
ERROR("invalid uid '%s'\n", args[2]);
free(tmp);
return;
}
uid = ret; //设置用户id
ret = get_android_id(args[3]); //得到android_id定义的id,具体见:android_ids表格,下一节会深入分析
if (ret < 0) {
ERROR("invalid gid '%s'\n", args[3]);
free(tmp);
return;
}
gid = ret; //设置group的id
add_dev_perms(name, attr, perm, uid, gid, prefix); //添加到链表中。
free(tmp);
}
在add_dev_perms()函数中,主要将/dev和/sys/ 分别添加到不同的链表中,具体的函数的实现如下:
int add_dev_perms(const char *name, const char *attr,
mode_t perm, unsigned int uid, unsigned int gid,
unsigned short prefix) {
struct perm_node *node = calloc(1, sizeof(*node));
if (!node)
return -ENOMEM;
node->dp.name = strdup(name);
if (!node->dp.name)
return -ENOMEM;
if (attr) {
node->dp.attr = strdup(attr);
if (!node->dp.attr)
return -ENOMEM;
}
node->dp.perm = perm;
node->dp.uid = uid;
node->dp.gid = gid;
node->dp.prefix = prefix;
if (attr)
list_add_tail(&sys_perms, &node->plist);
else
list_add_tail(&dev_perms, &node->plist);
return 0;
}
还未完,具体的分析在 《基于android2.3.5学习之:开天辟地Android启动机制[三]》 来分析。。。。