Vold是Android系统处理磁盘的核心部分,取代了原来Linux系统中的udev,主要用来处理Android系统的热插拔存储设备。在Android2.2以后的系统中,vold源码已经移到了system目录下,vold目录包含以下源码:
├── Android.mk
├── Asec.h
├── CleanSpec.mk
├── CommandListener.cpp
├── CommandListener.h
├── Devmapper.cpp
├── Devmapper.h
├── DirectVolume.cpp
├── DirectVolume.h
├── Fat.cpp
├── Fat.h
├── hash.h
├── logwrapper.c
├── Loop.cpp
├── Loop.h
├── main.cpp
├── NetlinkHandler.cpp
├── NetlinkHandler.h
├── NetlinkManager.cpp
├── NetlinkManager.h
├── Process.cpp
├── Process.h
├── ResponseCode.cpp
├── ResponseCode.h
├── vdc.c
├── VoldCommand.cpp
├── VoldCommand.h
├── Volume.cpp
├── Volume.h
├── VolumeManager.cpp
├── VolumeManager.h
├── Xwarp.cpp
└── Xwarp.h
先简要说明一下类的继承关系,vold中比较重要的有以下几个类:
三大管理类:VolumeManager,CommandListener,NetlinkManager
其他处理类:Volume,DirectVolume,NetlinkHandler,Fat,ResponseCode
其他相关的类:NetlinkListener,SocketListener
1.VolumeManager管理Volume类;
2.DirectVolume类继承于Volume类,保存着磁盘信息与操作函数;
3.NetlinkManager类负责与内核uevent事件通信,期间,使用到了NetlinkListener和SocketListener类的函数;
4.Fat是格式化sd卡的函数;
5.ResponseCode保存着vold向framework反馈的值。
本文讲解main.cpp文件的源代码:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
|
int
main() { /**********************************************************************************
**以下三个类声明三个指针对象:
**VolumeManager :管理所有存储设备(volume对象);
**CommandListener :监听Framework下发的消息,并分析命令,调用响应的操作函数;
**NetlinkManager :监听Linux内核的热插拔事件,uevent事件
**********************************************************************************/ VolumeManager *vm;
CommandListener *cl;
NetlinkManager *nm;
SLOGI( "Vold 2.1 (the revenge) firing up" );
/**********************************************************************************
**在Linux系统,如scsi硬盘,U盘的设备节点默认生成在/dev/目录下,Android把这些设备
**节点改到了/dev/block/目录下。但随着热插拔事件的产生,设备节点(如sda,sdb)经常变换,
**对于vold来说,可能有点麻烦,所以在/dev/block/下新建了一个名为vold的目录,存放sda,
**sdb对应的设备节点,形如"8:0"。
**eg:sda 的主次设备号分别为8,0,于是vold就会在vold目录下创建名为"8:0"的节点,基于主次设备号
**命名,便于程序操作,增加了灵活性。
**********************************************************************************/ mkdir( "/dev/block/vold" , 0755);
/**********************************************************************************
**实例化vm对象,VolumeManager类调用自身的Instance函数,new了一个对象给vm。
**源码:
VolumeManager *VolumeManager::Instance() {
if (!sInstance)
sInstance = new VolumeManager();
return sInstance;
}
**********************************************************************************/ if
(!(vm = VolumeManager::Instance())) { SLOGE( "Unable to create VolumeManager" );
exit (1);
};
/**********************************************************************************
**实例化nm对象,NetlinkManager类调用自身的Instance函数,new了一个对象给nm。
**源码:
NetlinkManager *NetlinkManager::Instance() {
if (!sInstance)
sInstance = new NetlinkManager();
return sInstance;
}
**********************************************************************************/ if
(!(nm = NetlinkManager::Instance())) { SLOGE( "Unable to create NetlinkManager" );
exit (1);
};
/**********************************************************************************
**实例化cl对象;
**vm->setBroadcaster((SocketListener *) cl);
**setBroadcaster函数将VolumeManager的成员变量mBroadcaster设置成cl,这两个变量都是
**SocketListener的指针类型,命令执行状态广播函数就会调用这个SocketListener指针来调用
**SocketListener类的广播函数;
**为什么SocketListener类能强制转换CommandListener类呢?
**原因:继承关系:CommandListener(子类) --> FrameworkListener(子类) --> SocketListener(父类)
**将子类强制转换为父类是没错的。
**********************************************************************************/ cl =
new CommandListener();
vm->setBroadcaster((SocketListener *) cl);
nm->setBroadcaster((SocketListener *) cl);
/**********************************************************************************
**调用start函数启动存储设备的管理类,看了源码,这函数没干什么事,估计去哪打酱油了。
**源码:
int VolumeManager::start() {
return 0;
}
**********************************************************************************/ if
(vm->start()) { SLOGE( "Unable to start VolumeManager (%s)" ,
strerror ( errno ));
exit (1);
}
/**********************************************************************************
**process_config函数用来解析/etc/vold.fstab的配置文件,从代码可以看出,配置文件的参数
**以空格和制表格(Tab键)分隔;系统启动起来,分析该配置文件,挂载相应的分区,相当于
**Linux系统的/etc/fstab文件。
**********************************************************************************/ if
(process_config(vm)) { SLOGE( "Error reading configuration (%s)... continuing anyways" ,
strerror ( errno ));
}
/**********************************************************************************
**nm对象调用start函数开启了一个线程,用来监听底层的uevent事件;这start函数干的事就
**多了,主要是打开一个udp套接字,循环监听底层事件。线程里面使用了Select函数来处理
**套接字,这设计到fd_set结构体等等的使用;
**当捕获到uevent事件,vold会将该事件通知给Framework层,Framework进行判断,然后再
**下发操作命令。
**********************************************************************************/ if
(nm->start()) { SLOGE( "Unable to start NetlinkManager (%s)" ,
strerror ( errno ));
exit (1);
}
coldboot( "/sys/block" );
/**********************************************************************************
**下面是判断Android系统是否处于ums状态,ums是大容量存储的意思,这是Android系统
**的OTG功能。OTG是on-the-go的简称,主要提供与pc机的连接;
**notifyUmsConnected函数将ums的状态通知给Framework层,于是Framework与UI配合,弹出
**一个与pc机连接的交互界面。
**********************************************************************************/ {
FILE
*fp; char
state[255]; if
((fp = fopen ( "/sys/devices/virtual/switch/usb_mass_storage/state" , "r" )))
{ if
( fgets (state,
sizeof (state), fp)) {
if
(! strncmp (state,
"online" , 6)) {
vm->notifyUmsConnected( true );
}
else {
vm->notifyUmsConnected( false );
}
}
else {
SLOGE( "Failed to read switch state (%s)" ,
strerror ( errno ));
}
fclose (fp);
}
else {
SLOGW( "No UMS switch available" );
}
}
/**********************************************************************************
**上面的准备工作已做完,现在是vold比较重要的一个处理线程;
**startListener是CommandListener类的父类的函数,该函数用于开启监听线程,监听
**Framework层下发给vold的命令,然后调用相应的命令操作存储设备。
**********************************************************************************/ if
(cl->startListener()) { SLOGE( "Unable to start CommandListener (%s)" ,
strerror ( errno ));
exit (1);
}
/**********************************************************************************
**进入一个循环,让vold保持守护进程的状态;
**vold的主要工作是由:nm->start()和cl->startListener()两个线程共同完成;这两个处理线程
**中间需要Framework来充当桥梁与boss的身份,Framework是管理这些磁盘的boss。
**********************************************************************************/ while (1) {
sleep(1000);
}
SLOGI( "Vold exiting" );
exit (0);
}
/**********************************************************************************
**以下这两个函数不重要,也就是打开/sys/block目录处理一些事情;这俩函数用来给vold打杂,
**社会阶级比较低,o(∩_∩)o 哈哈。
**里面有几个函数是bionic库提供的,用得比较少。
**********************************************************************************/ static
void do_coldboot(DIR *d,
int lvl)
{ struct
dirent *de; int
dfd, fd; dfd = dirfd(d);
fd = openat(dfd,
"uevent" , O_WRONLY);
if (fd >= 0) {
write(fd,
"add\n" , 4);
close(fd);
}
while ((de = readdir(d))) {
DIR *d2;
if
(de->d_name[0] == '.' )
continue ;
if
(de->d_type != DT_DIR && lvl > 0) continue ;
fd = openat(dfd, de->d_name, O_RDONLY | O_DIRECTORY);
if (fd < 0)
continue ;
d2 = fdopendir(fd);
if (d2 == 0)
close(fd);
else
{ do_coldboot(d2, lvl + 1);
closedir(d2);
}
}
} static
void coldboot( const
char *path) { DIR *d = opendir(path);
if (d) {
do_coldboot(d, 0);
closedir(d);
}
} /**********************************************************************************
**该函数用来解析/etc/vold.fstab配置文件,文本的处理;
**可能不同的源码版本,有点差异;
**strsep是字符串的分割函数,可以看出该函数是以" \t"来分割(\t前面有一空格),分割空格
**或制表格,所以配置文件里面空格与tab键来分割都行;
**strsep不是ANSI C的函数,但它用来取代strtok函数,strtok是线程不安全的函数。
**********************************************************************************/ static
int process_config(VolumeManager *vm) {
FILE
*fp; int
n = 0; char
line[255]; if
(!(fp = fopen ( "/etc/vold.fstab" ,
"r" ))) { return
-1; }
while ( fgets (line,
sizeof (line), fp)) {
char
*next = line; char
*type, *label, *mount_point; n++;
line[ strlen (line)-1] =
'\0' ; if
(line[0] == '#'
|| line[0] == '\0' )
continue ;
if
(!(type = strsep(&next, " \t" ))) {
SLOGE( "Error parsing type" );
goto
out_syntax; }
if
(!(label = strsep(&next, " \t" ))) {
SLOGE( "Error parsing label" );
goto
out_syntax; }
if
(!(mount_point = strsep(&next, " \t" ))) {
SLOGE( "Error parsing mount point" );
goto
out_syntax; }
if
(! strcmp (type,
"dev_mount" )) {
DirectVolume *dv = NULL;
char
*part, *sysfs_path; if
(!(part = strsep(&next, " \t" ))) {
SLOGE( "Error parsing partition" );
goto
out_syntax; }
if
( strcmp (part,
"auto" ) &&
atoi (part) == 0) {
SLOGE( "Partition must either be 'auto' or 1 based index instead of '%s'" , part);
goto
out_syntax; }
/**********************************************************************************
**如果配置文件指定为auto,则为自动挂载存储设备,在实例化DirectVolume的对象,传递-1
**进去,否则将分区序数part传进去;
**********************************************************************************/ if
(! strcmp (part,
"auto" )) {
dv =
new DirectVolume(vm, label, mount_point, -1);
}
else {
dv =
new DirectVolume(vm, label, mount_point,
atoi (part));
}
while ((sysfs_path = strsep(&next,
" \t" ))) {
/**********************************************************************************
**将存储设备在/sys/对应的路径添加进PathCollection容器,该容器为“char *”类型;
**在/sys/里面可以获取到存储设备的热插拔事件,所以DirectVolume类的主要工作就是针对
**这里去获取uevent事件的;
**DirectVolume::handleBlockEvent(NetlinkEvent *evt)函数去得到这些事件,主要还是
**NetlinkListener类从内核捕获到的。
**********************************************************************************/ if
(dv->addPath(sysfs_path)) { SLOGE( "Failed to add devpath %s to volume %s" , sysfs_path,
label);
goto
out_fail; }
}
/**********************************************************************************
**如果在配置文件有找到正确的挂载参数,那么就会将DirectVolume的对象添加到VolumeCollection
**容器中,该容器存放着Volume*类型的数据,VolumeManager的对象vm是用来管理这些存储设备的;
**一块存储设备就会实例化一个Volume对象,但对于手机来说,一般只能识别到一张SD卡。
**********************************************************************************/ vm->addVolume(dv);
}
else
if (! strcmp (type,
"map_mount" )) {
}
else {
SLOGE( "Unknown type '%s'" , type);
goto
out_syntax; }
}
fclose (fp);
return
0; /**********************************************************************************
**从这个main函数的出错处理可以看出,系统源码经常使用到这种高效性的goto技巧,goto在
**系统中的出错处理用得很频繁,可以说几乎每个文件都使用到了goto跳转函数;
**很多文章或者教材,经常反面性的批判goto的不规则,但从这些外国的开源代码可以看出,
**那些牛人都很喜欢用goto,利用了goto来处理出错情况的技巧,显得很漂亮;
**我觉得,要从实用性的角度来评论这些语言的优缺点,并不能用否认的说法来解释,这样才能
**不断地进步;
**所以,如果在出错处理非常多的情况下,使用goto是使代码更可读,减少重复的出错判断的
**代码量。
**********************************************************************************/ out_syntax:
SLOGE( "Syntax error on config line %d" , n);
errno
= -EINVAL; out_fail:
fclose (fp);
return
-1; } |