本文轉載自:https://blog.csdn.net/kc58236582/article/details/49618445
1 L1813系統上雙U盤設計方案——系統設計
1.1 方案背景
Android原生的磁盤管理方案,設計的思想是將EMMC上的空間通過sdcard的server模擬成一個StorageVolume,供用戶作為外部存儲空間使用,而該部分空間是不能通過切換大容量存儲在PC側供用戶使用,只能通過MTP協議,將手機和PC側互聯,從而在PC側讀取手機側的外部存儲空間的內容。Android原生也是留出了給外置SD卡的設計接口,允許外置SD卡通過大容量存儲的方式同PC側相連接。
圖2.1.1 Android原生設計存儲方案
這里需要分清一個概念,Android原生設計的外部存儲空間,和我們常規意義上理解是不同的,Android原生將所有用戶可見的存儲空間都定義為外部存儲空間,而本文以下所指的外部存儲空間,特指外置SD卡所表示的存儲空間。而本文以下所指的內部存儲空間(內置U盤),特指從EMMC上獨立划分出來的一個分區所表示的手機EMMC上的一塊存儲空間。本文所指的雙U盤方案,即指的是在Android系統上,同時存在外部存儲空間和內部存儲空間,並且在切換大容量存儲的時候,可以在PC側同時出現兩個可移動磁盤。
圖2.1.2 Android雙U盤生設計存儲方案
雙U盤方案涉及到Android系統磁盤存儲的整體框架,從APP,Framework,Vold,Kernel幾個層次進行整體修改和設計。從EMMC上縮小userdata分區大小,將新增加空間做為一塊固定大小的分區,作為內置U盤分區,而外置的SD卡作為外置U盤分區,從而形成雙U盤的方案。系統運行時,需要保證能在不重啟的前提下,動態的切換默認存儲的分區,並保證上層應用的穩定性和安裝第三方應用的正確性。本文將從底向上的層次順序描述該方案。
1.2 Bootloader層對EMMC的分區調整
2.2.1背景知識
在嵌入式操作系統中,分區只是內核的概念,就是說A~B[p1] 地址放內核,C~D地址放文件系統,(也就是規定哪個地址區間放內核或者文件系統)等等,而對於bootloader中只要能將內核下載到A~B區的A地址開始處就可以,C~D區的C起始地址下載文件系統。。。這些起始地址在kernel的分區信息中能找到。所以bootloader對分區的概念不重要,只要它能把內核燒到A位置,把文件系統燒到C位置。
所以,在bootloader對Flash進行操作時,哪塊區域放什么是以內核為主,而為了方便操作,bootloader類似也引入分區的概念。所以,如果你是通過uboot的內核命令行給kernel層傳遞分區信息,這種情況下,內核讀取到的分區信息始終和u-boot中的保持一致(推薦的做法);而如果你是把分區信息寫在內核源代碼里定義好的方法,那最好保證它和u-boot中的保持一致,即同步修改uboot及內核的相關部分。
目前L1813的kernel版本已經不支持通過uboot的內核命令行給kernel傳遞分區信息的方式,目前采用的是一種在uboot里就配置好所有分區信息,從而直接使用。
2.2.2 L1813針對雙U盤的分區調整方法
目前,L1813方案的分區設置是在bootable/bootloader/uboot/include/configs/下針對不同項目有不同的.h的配置文件,以ust802手機的P0版手機為例,需要查看並修改comip_ust802_v1_0.h的相關分區信息。
2.2.2.1分區結構體
struct partition {
ulong sector_size;
ulong start;
ulong length;
const char *name;
};
成員名 |
描述 |
sector_size |
每個扇區大小 |
start |
分區起始地址 |
length |
分區長度 |
char *name |
分區名稱 |
2.2.2.2 分區詳細信息
每個扇區大小(單位:字節) |
分區起始位置 |
分區所占扇區數目(分區長度) |
分區名稱 |
512(0x200) |
0x00000000 |
0x00000100 |
Uboot |
512(0x200) |
0x00000400 |
0x00000400 |
lcboot |
512(0x200) |
0x00000800 |
0x00002000 |
logo |
512(0x200) |
0x00002800 |
0x00000800 |
fota |
512(0x200) |
0x00003000 |
0x00000800 |
panic |
512(0x200) |
0x00003800 |
0x00001000 |
amt |
512(0x200) |
0x00004800 |
0x00008000 |
modemarm |
512(0x200) |
0x0000C800 |
0x00001620 |
modemdsp0 |
512(0x200) |
0x0000DE20 |
0x000009E0 |
modemdsp1 |
512(0x200) |
0x0000E800 |
0x00002000 |
kernel |
512(0x200) |
0x00010800 |
0x00000800 |
ramdisk |
512(0x200) |
0x00011000 |
0x00000800 |
ramdisk_amt1 |
512(0x200) |
0x00011800 |
0x00000800 |
ramdisk_amt3 |
512(0x200) |
0x00012000 |
0x00000800 |
ramdisk_recovery |
512(0x200) |
0x00012800 |
0x00002000 |
kernel_recovery |
512(0x200) |
0x00018000 |
0x00001000 |
misc |
512(0x200) |
0x00020000 |
0x00040000 |
cache |
512(0x200) |
0x00060000 |
0x000c0000 |
system |
512(0x200) |
0x00120000 |
userdata |
圖1原ust802_v1_0手機分區詳表
2.2.2.3分區修改方法
雙U盤方案需要將EMMC上縮小userdata[p2] 分區的1G空間,將該空間作為一個1G大小的新分區,稱為udisk分區,作為內置存儲空間。
調整后的分區信息如下標紅部分(udisk分區從userdata分區頭部划分,是為了方便后期調整EMMC大小時,可以直接從userdata分區尾部調整):
每個扇區大小(單位:字節) |
分區起始位置 |
分區所占扇區數目(分區長度) |
分區名稱 |
0x00000000 |
0x00000100 |
uboot |
|
512(0x200) |
0x00000400 |
0x00000400 |
lcboot |
512(0x200) |
0x00000800 |
0x00002000 |
logo |
512(0x200) |
0x00002800 |
0x00000800 |
fota |
512(0x200) |
0x00003000 |
0x00000800 |
panic |
512(0x200) |
0x00003800 |
0x00001000 |
amt |
512(0x200) |
0x00004800 |
0x00008000 |
modemarm |
512(0x200) |
0x0000C800 |
0x00001620 |
modemdsp0 |
512(0x200) |
0x0000DE20 |
0x000009E0 |
modemdsp1 |
512(0x200) |
0x0000E800 |
0x00002000 |
kernel |
512(0x200) |
0x00010800 |
0x00000800 |
ramdisk |
512(0x200) |
0x00011000 |
0x00000800 |
ramdisk_amt1 |
512(0x200) |
0x00011800 |
0x00000800 |
ramdisk_amt3 |
512(0x200) |
0x00012000 |
0x00000800 |
ramdisk_recovery |
512(0x200) |
0x00012800 |
0x00002000 |
kernel_recovery |
512(0x200) |
0x00018000 |
0x00001000 |
misc |
512(0x200) |
0x00020000 |
0x00040000 |
cache |
512(0x200) |
0x00060000 |
0x000c0000 |
system |
512(0x200) |
0x00120000 |
0x00200000 |
udisk |
512(0x200) |
0x00320000 |
0x003e0800 |
userdata |
圖2 ust802_v1_0手機雙U盤分區詳表
將userdata分區原大小0x005e0800*0x200=2.94G縮小為0x003e0800*0x512=1.94G,
將該1G空間的大小分給udisk分區。
同時在BoardConfig.mk下,需要調整BOARD_USERDATAIMAGE_PARTITION_SIZE,由於USERDATAIMAGE有1M空間需要進行加密,因而在此處的userdata分區的空間調整需要比實際userdata分區小1M。[p3]
修改前,BOARD_USERDATAIMAGE_PARTITION_SIZE為3154116608字節,
修改后,BOARD_USERDATAIMAGE_PARTITION_SIZE為2080374784字節。[p4]
2.2.3 Bootloader下對本功能宏的控制
//TODO
1.3 修改SML燒錄工具
由於修改了EMMC的分區表,對應在燒錄版本的時候就需要修改SML工具,在userdata分區之前,新增加一個udisk分區,並將分區大小對應修改。
在SML工具根目錄下,進入/Inifiles文件夾,找到對應項目的.pks配置文件(pks文件實際為zip壓縮文件),L1813項目為LC1813-2.0.00-FEATURE.pks,解壓后,找到DTL1813MicronUSB_HS_large.ini配置文件,修改相應分區配置項:
原配置項:
[DOWNLOADTYPE18]
DownloadTypeName=userdata
StartAddress=0x24000000
Type=0
Size=3221225472 #0xC0000000
修改為:
[DOWNLOADTYPE18]
DownloadTypeName=udisk
StartAddress=0x24000000
Type=0
Size=1073741824#0x40000000
[DOWNLOADTYPE19]
DownloadTypeName=userdata
StartAddress=0x64000000
Type=0
Size=2147483648 #0x80000000
完成后,壓縮成zip文件,更改后綴名為pks文件,替換元Inifiles目錄下的對應pks文件。
1.4 Linux Kernel層對USB驅動的調整
2.4.1背景知識
Linux kernel2.6以上的版本中,USB設備驅動的接口改為了gadget,在kernel/driver/usb/gadget目錄下主要包含了平台USBUDC驅動和gadget接口驅動。Linux支持連接各種USB從設備,同時也支持自己作為設備插入到其他主機當中。最典型的例子就是AndroidOS的手機,插入電腦可以被識別為U盤之類的設備。
為了避免與作為主機時支持的"設備驅動(USB Device Driver)"一詞混淆,Linux給這部分的實現取名為"Gadget",小玩具。內核源碼的目錄為\drivers\usb\gadget,里面包含了內核所支持的不同類型的USBDevice Controller (UDC)驅動的實現,以及框架和不同gadget的實現。
2.4.2 USB Gadget的三層架構
Linux USB Gadget分三層架構,層次關系從上到下:
一層:USB Gadget功能層。BSP/Driver開發者通常是要實現這一層,從而實現一個具體的設備驅動,如Anddroid在此層實現了adb,mtp,mass_storage等。瀏覽參考關注此層代碼時,會發現“composite”是此層的關鍵字,此層中關鍵的數據結構是:struct usb_composite_driver。這一層的驅動文件一般為:driver/usb/gadget/android.c(android實現的)或driver/usb/gadget/serial.c(傳統Linux實現的USB轉串口)。
二層:USB設備層。這一層是Linux內核開發維護者實現的,與我們沒太大關系,不用我們操心,我們只關心其的一些接口就行。瀏覽參考關注此層時,會發現“gadget”是此層的關鍵字,此層的關鍵數據結構是:usb_gadget_driver,usb_composite_dev。這層主要的一個驅動文件為:driver/usb/gadget/composite.c
三層:USB設備控制器驅動層。這一層主要是與CPU、CPU USB控制器有關,與硬件緊密相關,這一層也比較頭痛,主要它和USB控制器牽扯在一起,涉及有寄存器、時鍾、DMA等等。但是這一層往往是由芯片廠商去實現。我們一般僅需在板級文件中處理好所需要的USB接口即可。這層的關鍵字就是“UDC”,主要驅動文件命名含“udc”關鍵字,一般與CPU或芯片廠商有關,如driver/usb/gadget/xxx_udc.c。
可以用一句簡單的話去概括三層的關系:USB Gadget功能層調用USB設備層的接口,USB設備層調用USB設備控制器驅動層的接口,然后USB設備控制器驅動層回調USB設備層,USB設備層回調USB Gadget功能層。
2.4.3 調整USB gadget的f_mass_storage
如上所述,我們主要關注的是三層里面的第一層:USB Gadget功能層。在這一層里,重點關注Android自己實現了的一個驅動文件driver/usb/gadget/android.c。
2.4.3.1 USBFunction結構體
目前在Android4.2上支持的所有USB Function如下:
static struct android_usb_function*supported_functions[] = {
&ffs_function,
&adb_function,
&serial_function,
//&acm_function,
&mtp_function,
&ptp_function,
&rndis_function,
&mass_storage_function,
&accessory_function,
&audio_source_function,
NULL
};
對於本文所關注的內置U盤功能,主要是涉及到mass_storage_function:
static struct android_usb_functionmass_storage_function = {
.name ="mass_storage",
.init =mass_storage_function_init,
.cleanup = mass_storage_function_cleanup,
.bind_config = mass_storage_function_bind_config,
.attributes = mass_storage_function_attributes,
};
默認情況下,mass_storage_function只設定了一個大容量存儲空間,鑒於本功能的要求,需要將默認的config配置為可以使用兩塊大容量存儲空間。
控制mass_storage_function的主要結構體是mass_storage_function_config,其成員變量fsg_config可以控制需要使用幾塊LUN的設備。
我們需要將其抽象出接口函數,可供動態控制。
//TODO
2.4.4 kernel層對本功能的宏控制
//TODO
1.5 格式化新分區
2.5.1需要格式化的原因
在bootloader對EMMC分區后,目前對分區的格式是使用EXT4文件系統:
/dev/block/platform/comip-mmc.1/by-name/userdata /data ext4 noatime,nosuid,nodev,barrier=1,data=ordered,noauto_da_alloc wait,check,encryptable=footer
而對內置U盤而言,由於需要切換到PC側做大容量存儲功能,EXT4的文件系統對Windows操作系統而言是不可識別的,因此需要對udisk分區做額外的格式化,將其格式化為FAT格式。
2.5.2添加格式化的時機
對EMMC分區的FAT格式化,常規考慮是在手機燒錄完版本第一次開機后,進行FAT的格式化操作。但在實際生產過程中,有時會發現,某些EMMC的硬件特殊性,導致在生產線上,偶爾會出現手機燒錄完版本第一次開機后,調用Linux的FAT格式化腳本失敗的情況,導致手機無法使用內置U盤的功能。此類問題,需要在產線上就被過濾掉,因此,需要考慮在產線的校驗功能中添加這一格式化檢驗功能。
另外,在手機正常升級版本的情況下,也需要添加格式化內置U盤的功能。而正常升級版本是不會操作AMT分區的數據的,因此還需要提供一種機制,能保證在擦除userdata分區數據的同時,對udisk分區進行格式化。
2.5.3在生產校驗中添加格式化udisk操作
2.5.3.1快速AMT3介紹及其原理分析
//TODO
2.5.3.2在快速AMT3中添加腳本
//TODO
2.5.4添加格式化腳本
在本方案的設計中,添加腳本對該內置U盤空間進行格式化,將其格式化為FAT的文件系統。
2.5.4.1腳本啟動方法
該腳本的啟動方法為在手機燒錄完版本后,第一次開機時,進行格式化操作。操作成功,在amt分區置一個標志位文件,以后每次開機時,先讀取該標志位文件,如果之前格式化成功,就不在運行該腳本,否則將再次運行該腳本,直到格式化成功。
修改點為,在init.lc1813.rc里添加一個新的service用於啟動格式化udisk分區的腳本:
service format_udisk /system/bin/sh/system/bin/mmc_format.sh udisk
class core
oneshot
編譯時,在device.mk里將格式化udisk分區的腳本放置到手機system/bin:
#format internal sdcard.
PRODUCT_COPY_FILES += \
device/leadcore/ust802p0/mmc_format.sh:system/bin/mmc_format.sh
2.5.4.2格式化腳本分析
真正處理格式化的腳本是mmc_format.sh,其主要流程為,通過啟動busybox的mkfs.vfat命令,格式化指定EMMC的分區,並在amt分區放置標志位文件,用以表示是否格式化成功。
腳本關鍵步驟命令:
MMC_DEV=/dev/block/mmcblk0 //指定EMMC分區
UDISK_PART=${MMC_DEV}p18//指定EMMC分區
FAT_MKFS="busyboxmkfs.vfat"//格式化命令
2.5.4.3正常升級能格式化udisk分區的方法
以上的方法只能保證手機第一次開機后格式化udisk分區,而在日常的使用中,由於手機軟件版本的升級,往往需要對EMMC的軟件分區都做大版本的升級,而做軟件版本的升級時,是不會對AMT分區進行升級的,因為AMT分區保留的是手機的射頻和Modem的相關數據。因此,在AMT的標志位文件也不會進行修改,而大版本的升級會對userdata分區進行擦除,因此可以根據userdata分區的數據的擦寫情況,決定是否需要對udisk分區進行格式化操作。
在第一次燒錄版本時,拷貝一個標志文件.first_flash到data分區:
PRODUCT_COPY_FILES += \
device/leadcore/ust802p0/first_flash:data/.first_flash
手機啟動后,在mmc_format.sh腳本里對該標志文件進行控制。如果該標志文件存在,則表示擦寫過userdata分區,需要格式化udisk分區,格式化后刪除該標志文件。如果,該標志文件不存在,則表示是一次正常的開機,沒有擦寫過userdata分區,不需要格式化udisk分區。
udisk)
FILE=/amt/udisk_format.txt
FILE_FIRST_FLASH=/data/.first_flash
if [[ -e $FILE && ! -e$FILE_FIRST_FLASH ]] ; then
echo "start mount udisk "
#busybox mount -r -w -t vfat/dev/block/mmcblk0p18 /udisk
exit 2
fi
FORMAT_NUM_TOTAL=3
FORMAT_NUM=0
while [ "$FORMAT_NUM" !="$FORMAT_NUM_TOTAL" ]
do
$FAT_MKFS $UDISK_PART
covert_ret $?
ret=$?
FORMAT_NUM=`busybox expr$FORMAT_NUM + 1`
setprop $RESOULT_PROP end=$ret
if [ "$ret" ="1" ] ; then
echo "format udisk ok ! In$FORMAT_NUM" >> $FILE
if [ -e $FILE_FIRST_FLASH ];then
rm $FILE_FIRST_FLASH
fi
exit 2
fi
done
;;
1.6 啟動腳本init.rc
2.6.1背景知識
init進程,它是一個由內核啟動的用戶級進程。內核自行啟動(已經被載入內存,開始運行,並已初始化所有的設備驅動程序和數據結構等)之后,就通過啟動一個用戶級程序init的方式,完成引導進程。init始終是第一個進程.
2.6.2調整啟動腳本init.rc
在init.rc里,有多個地方涉及到存儲系統的路徑和全局變量等,因此需要做一定的調整來適應雙U盤的方案。
1.6.1.1 調整全局變量[p5]
全局變量名 |
全局變量值 |
修改說明 |
INTERNAL_STORAGE |
/storage/sdcard0 |
sdcard0對應內置U盤 |
EXTERNAL_STORAGE |
/storage/sdcard1 |
sdcard1對應外置SD卡 |
表2.6.2.1雙U盤方案涉及的全局變量
1.6.1.2 調整存儲空間路徑和相關軟鏈接
方法 |
路徑和相關軟鏈接 |
權限 |
修改說明 |
mkdir |
/storage/sdcard0 |
0555 root root |
新建內置U盤目錄 |
mkdir |
/storage/sdcard1 |
0555 root root |
新建外置SD卡目錄 |
symlink |
/storage/sdcard0 /sdcard |
NA |
添加軟鏈接 |
symlink |
/storage/sdcard0 /mnt/sdcard |
NA |
添加軟鏈接 |
symlink |
/storage/sdcard1/mnt/sdcard2 |
NA |
添加軟鏈接 |
表2.6.2.2雙U盤方案涉及的存儲空間路徑和相關軟鏈接
1.6.1.3 取消原生的模擬sdcard服務
由於userdata分區的文件系統格式為EXT4,此類格式對文件的權限有嚴格的控制,而原生的Android設計中,是將userdata分區模擬成內置SD卡,對內置SD卡,是不能有如此嚴格的權限控制,因此Android原生設計了一套sdcard的service,通過該服務模擬userdata分區下的/data/media目錄為/mnt/shell/emulated[p6] ,權限為media_rw(1023),將該目錄模擬成fuse格式的文件系統,fuse文件系統可以提供普通sdcard一樣的對文件的讀寫權限。
在本方案的設計中,不在需要該套sdcard的service,因此需要在init中,將該服務注釋掉。
1.7 Vold層
2.7.1Vold磁盤管理框架介紹
1.7.1.1 Vold的產生和udev
udev是 Linux2.6內核里的一個功能,它替代了原來的 devfs,成為當前 Linux 預設的設備管理工具。udev以守護進程的形式運行,通過偵聽內核發出來的 uevent來管理 /dev目錄下的設備文件。不像之前的設備管理工具,udev在用戶空間 (user space)運行,而不在內核空間 (kernel space)運行。
Vold的全稱是Volume Daemon。在android中,取代udev的是vold, android一出生就沒有遵守傳統linux的許多標准,所以udev也不能很好的服務於android。android的的做法是定做一套udev,這就是vold。無論是udev還是vold,都是基於sysfs的,sysfs為內核與用戶層的通訊提供了一種全新的方式,並將這種方式加以規范。kernel層能檢測到有新的設備接入,並能為之加載相應的驅動,sysfs用於通知用戶層,內核中的sysfs機制要求當有新的驅動加載時給用戶層發送相應的event。Vold負責具體處理。對於用戶層而言,無需關心sysfs的細節,只要知道sysfs可以向用戶層提供信息即可。首先,我們要知道如何接收來自內核的event。這里就要用到Netlink socket,socket不僅能用於網絡間的通訊, 也用能用於進程間的通訊,而這種內核態與用戶溝通的活,也需要使用socket。
Vold是存儲類的守護進程,是Android系統處理磁盤的核心部分。Vold服務由volumeManager統一管控,它將具體任務分別分派給netlinkManager,commandListener, directVolume, Volume去完成。Vold服務向下通過socket機制與底層驅動交互,向上通過JNI,intent, socket, doCommand等機制與Java Framework交互。
Android的volume服務主要是用來管理usb/sd卡等外部存儲設備。平台可以對外部存儲設備進行操作和輪詢狀態,當外部存儲設備狀態發生變化時,volume服務也會實時報告平台。
1.7.1.2 Vold啟動
VolumeDaemon是在android init進程中啟動的。在init進程中將解析init.rc文件。在該文件中有啟動Vold的配置。如下:
service vold /system/bin/vold
class core
socket vold stream 0660 rootmount
ioprio be 2
在這里將啟動Vold Daemon,並且創建一個socket。該socket主要是為了與framework層通信。
1.7.1.3 Vold內部結構
Vold(Volume Daemon)的內部框架如下圖所示。Vold處理過程大致分為三步:創建連結、引導和事件處理。下面將結合下圖對Vold工作流程進行分析。
Vold 內部架構
Vold作為一個守護進程,一方面接受驅動的信息,並把信息傳給應用層;另一方面接受上層的命令並完成相應。所以這里的連結一共有兩條:
1) vold socket:負責vold與framework層的信息傳遞;
2)接受sysfs uevent的socket:負責接受kernel的信息;
Vold socket是在init進程啟動VolumeDaemon時創建的,這是一個用於和framework層通信的socket,在android系統中叫做Localsocket,framework層通過JNI機制調用C/C++空間函數與之通訊。
而與內核通信的socket是在vold main函數中創建的。在main函數中,vold將創建兩個單例,VolumeManager和NetLinkManager,與內核通信的socket就在其中創建。這樣Volume Daemon與Kernel、framework層的通信框架就建立。
1.7.1.4 Uevent和Netlink簡介
uevent由內核發出,通過netlink sokect來傳遞給vold,在kobject被創建的時候,就會發生uevent的傳遞。對於未傳遞的uevent,會在kset下產生uevent文件,這是供用戶態觸發uevent使用的,通過向uevent檔寫入action(add,remove等),可以觸發一個uevent,這些uevent可以被vold捕獲,從而完成未完成的vold處理。在系統啟動的時候,vold未啟動的時候,這些uevent寫入了uevent,vold啟動后,會掃描sys目錄查找uevent,然后觸發它們,來完成之前未完成的事宜。uevent文件的內容,就是uevent事件的數據。
Netlink socket,socket不僅能用於網絡間的通訊,也用能用於進程間的通訊,像這種內核態與用戶溝通的活,自然也少不了它。在 Linux 2.4 版以后版本的內核中,幾乎全部的中斷過程和使用者態進程的通信都是使用 netlink套接字實現的。netlink套接字的最大特點是對中斷過程的支持,他在內核空間接收用戶空間數據時不再需要用戶自行啟動一個內核線程,而是通過另一個軟中斷調用用戶事先指定的接收函數。工作原理如圖。
用戶空間,用戶態應用使用標准的socket和內核通訊,標准的socketAPI的函數,socket(),bind(), sendmsg(), recvmsg()和 close()非常容易地應用到 netlink socket。
為了創建一個 netlink socket,使用者需要使用如下參數調用 socket() socket(AF_NETLINK, SOCK_RAW, netlink_type) netlink對應的協議簇是AF_NETLINK,第二個參數必須是SOCK_RAW或SOCK_DGRAM,第三個參數指定netlink協議類型,他能是個自定義的類型,也能使用內核預定義的類型:
#defineNETLINK_KOBJECT_UEVENT 15 /* Kernel messages to userspace */
同樣地,socket函數返回的套接字,能交給bind等函數調用:
static int skfd;
skfd = socket(PF_NETLINK, SOCK_RAW, NL_IMP2);
bind函數需要綁定協議地址,netlink的socket地址使用struct sockaddr_nl架構:
struct sockaddr_nl
{ sa_family_t nl_family;
unsigned short nl_pad;
__u32 nl_pid;
__u32 nl_groups; };
成員 nl_family為協議簇 AF_NETLINK,成員 nl_pad當前沒有使用,因此要總是設置為 0,成員 nl_pid 為接收或發送消息的進程的ID,如果希望內核處理消息或多播消息,就把該字段設置為0,否則設置為處理消息的進程ID。成員 nl_groups用於指定多播組,bind函數用於把調用進程加入到該字段指定的多播組,如果設置為0,表示調用者不加入所有多播組:
struct sockaddr_nl local;
memset(&local, 0, sizeof(local));
local.nl_family = AF_NETLINK;
local.nl_pid = getpid(); /*設置pid為自己的pid值*/
local.nl_groups = 0; /*綁定套接字*/
if(bind(skfd, (structsockaddr*)&local, sizeof(local)) != 0)
{
printf("bind() error\n"); return -1;
}
用戶空間能調用send函數簇向內核發送消息,如sendto、sendmsg等,同樣地,也能使用struct sockaddr_nl來描述一個對端地址,以待send函數來調用,和本地地址稍不同的是,因為對端為內核,所以nl_pid成員需要設置為0:
struct sockaddr_nl kpeer;
memset(&kpeer, 0, sizeof(kpeer));
kpeer.nl_family = AF_NETLINK;
kpeer.nl_pid = 0; kpeer.nl_groups = 0;
rcvlen = recvfrom(skfd, &info, sizeof(struct u_packet_info),
0, (struct sockaddr*)&kpeer, &kpeerlen);
/*處理接收到的數據*/ }
同樣地,函數close用於關閉打開的netlinksocket.
1.7.1.5 處理來自framework層發出的command
class CommandListener主要收到上層 MountService通過doMountVolume發來的命令,分析后,轉交給VolumeManager處理;VolumeManager處理信息后,或報告給上層MountService,或交給volume執行具體操作。
1.7.1.6 Vold.fstab分區表的介紹
Android系統上由於使用了Vold取代了udev來管理磁盤,自然也要有一個類似於udev的config來配置和管理磁盤的掛載,Android使用的是vold.fstab來實現這一目的。通過vold.fstab,Android可以讀取預先配置好的sdcard或者多分區配置文件,該配置文件的格式和解析意義如下表:
格式 |
說明 |
示例 |
Format |
掛載格式 |
dev_mount |
Label |
掛載標簽 |
sdcard |
Mount_point |
掛載點 |
storage/sdcard1 |
Part |
掛載分區(auto為自動掛載) |
auto |
<sys_path> |
設備實際路徑 |
/devices/platform/comip-mmc.0/mmc_host/mmc1 |
表2.7.2 Vold.fstab分區表
需要注意的是:
1)子分區的數目可以為auto,表示只有一個子分區。子分區數目也可以為任意大於0的一個整數。
2)個參數間不能有空格,應該以tab制表符為參數的間隔,原因是android對vold.fstab的解析是以”\t”為標識,從而得到各個參數。
2.7.2 動態加載VOLD的分區表fstab
目前該文件位於device/leadcore/【項目名】/。該配置文件在Vold起到的配置外置存儲器掛載點的作用[p7] 。調整vold.fstab,可以將內置U盤和外置SD卡分別掛載到vold需要解析的fstab上,讓vold及以上層次能看到該FAT分區。
在Android原生設計里,只能讀取一個配置好的fstab,無法實現動態加載。
在本雙U盤的設計里,可以通過上層應用接口動態的調整當前的默認存儲器。故在Vold層,需要可以根據動態的設定而決定當次加載的是那一個存儲器。因此,本設計在Vold加載vold.fstab時,就動態的根據配置的默認存儲器來決定當次加載的主存儲器是內置U盤還是外置SD卡。
由於Android的傳統習慣,將/storage/sdcard0/作為默認的存儲器,因此,設計了兩個fstab配置文件分別為:vold_internal.fstab,vold_external.fstab。分別表示內置U盤為默認存儲器和外置SD卡為默認存儲器的情況。再在Vold的process_config()函數里,解析fstab函數[p8] 時,根據當前配置的storage.config所指的默認存儲器的值來決定本次加載的是哪一個fstab配置文件。
而當外置SD卡沒有插入時的開機,需要做好特殊處理,強制設置為內置U盤為默認主存儲器。
格式 |
說明 |
內置U盤 |
Format |
掛載格式 |
dev_mount |
Label |
掛載標簽 |
Udisk |
Mount_point |
掛載點 |
storage/sdcard0 |
Part |
掛載分區(auto為自動掛載) |
18 |
<sys_path> |
設備實際路徑 |
/devices/platform/comip-mmc.1/mmc_host/mmc0/mmc0 |
格式 |
說明 |
外置SD卡 |
Format |
掛載格式 |
dev_mount |
Label |
掛載標簽 |
Sdcard |
Mount_point |
掛載點 |
storage/sdcard1 |
Part |
掛載分區(auto為自動掛載) |
Auto |
<sys_path> |
設備實際路徑 |
/devices/platform/comip-mmc.0/mmc_host/mmc1 |
表2.7.3.1雙U盤方案的vold_internal.fstab分區表
格式 |
說明 |
內置U盤 |
Format |
掛載格式 |
dev_mount |
Label |
掛載標簽 |
Udisk |
Mount_point |
掛載點 |
storage/sdcard1 |
Part |
掛載分區(auto為自動掛載) |
18 |
<sys_path> |
設備實際路徑 |
/devices/platform/comip-mmc.1/mmc_host/mmc0/mmc0 |
格式 |
說明 |
雙U方案:外置SD卡掛載 |
Format |
掛載格式 |
dev_mount |
Label |
掛載標簽 |
Sdcard |
Mount_point |
掛載點 |
storage/sdcard0 |
Part |
掛載分區(auto為自動掛載) |
Auto |
<sys_path> |
設備實際路徑 |
/devices/platform/comip-mmc.0/mmc_host/mmc1 |
表2.7.3.2雙U盤方案的vold_external.fstab分區表
2.7.3 調整MAX_PARTITIONS
在Vold的卷管理類DirectVolume里,由於一般的SD卡的分區不會超過4個分區,所以Android默認設置的對每個SD卡的最大分區為4。在本需求的修改中,由於在EMMC上目前的分區大大多於4,一般在15~20個分區(視項目而定),因此需要調整該參數設置。
static const int MAX_PARTITIONS = 32;
2.7.4 增加對多個大容量存儲磁盤的適配
由於雙U盤需要兩個大容量存儲設備,因而需要調整VolumeManager.cpp內對大容量存儲設備的管理。
對應kernel的修改,添加內置U盤的大容量存儲的路徑:
MASS_STORAGE_FILE_PATH_1 "/sys/class/android_usb/android0/f_mass_storage/lun1/file"
在VolumeManager.cpp的shareVolume和unshareVolume函數里,需要增加一組設備的打開和關閉大容量存儲的操作。
修改函數:int VolumeManager::shareVolume(const char *label, const char*method);
int VolumeManager::unshareVolume(const char *label, const char*method);
1.8 Framework層
2.8.1Framework層的磁盤管理框架
Android的原生設計,遵從軟件工程的“高內聚,低耦合”的設計思想,在Framework層有一套獨立的磁盤管理框架,起到承上啟下的作用。承上,是指同APP層的交互,接受UI層發送過來的命令,或者將底層Vold上報的磁盤狀態進行處理更新,上傳給APP層;啟下,是同Vold通訊,將APP層下發的對磁盤的操作指令傳遞給Vold,或者接受底層傳來的磁盤狀態更新的消息上報給APP層。而起到這一關鍵的作用的,就是Mountservice。
MountService作為Android的Java服務之一,在SystemServer進程啟動的第二階段創建並注冊到ServiceManager中,同時長駐於SystemServer進程中。MountService各個類關系如下圖:
直接查看4.4.4上a310p1上的代碼,fstab.lc1860中每有Mount_point這一項
只有:voldmanaged=sdcard1::udisk,noemulatedsd
在開始構造MountService的時候,有一個非常重要的配置文件,storage_list.xml[p1] 文件,該文件以XML方式保存了所有存儲設備的參數。Mountservice就是通過使用XML解析器讀取該XML的文件內容,根據讀取到的存儲設備參數來構造StorageVolume對象,並將構造的所有StorageVolume對象存放到列表mVolumes中。同時,Mountservice注冊了一個廣播接收器,用於接收開機完成廣播及USB狀態廣播,當開機完成時自動掛載存儲設備,在大容量設備存儲有效情況下,當USB狀態變化也自動地掛載存儲設備。
從上圖可以清晰地看出SystemServer主線程啟動MountService服務,該[p2] 服務啟動時會創建一個MountService帶有消息循環的工作線程,用於處理MountServiceHandle和ObbActionHandler分發過來的消息;同時創建一個用於連接Vold服務端socket的VoldConnector線程,該線程在進入閉環運行前會創建一個帶有消息循環的VoldConnector.CallbackHandler線程,用於處理native層的Vold進程發送過來的uevent事件消息;然后向服務端Vold發送連接請求,得到socket連接后,從該socket中循環讀取數據以接收來之服務端Vold的uevent消息,當讀取的數據長度為0時,向服務端重新發起連接,如此循環,保證客戶端MountService與服務端Vold一直保持正常連接。當成功連接到服務端Vold時,VoldConnector線程會創建一個MountService#onDaemonConnected線程,用於處理本次連接請求響應[p3] 。
MountService駐留在SystemServer進程中,和Vold作為兩個不同的進程,它們之間的通信方式采用的是socket通信,Vold在CommandListener模塊啟動了一個socket監聽線程,用於專門接收來之上層MountService的連接請求。而在MountService這端,同樣啟動了VoldConnector socket連接線程,用於循環連接服務端,保證連接不被中斷,當成功連接Vold時,循環從服務端讀取數據。MountService按照指定格式向Vold發送命令。下圖表示了其中的mount命令的發送和接收流程。
2.8.2 雙U盤策略下的Framework層的磁盤管理
2.8.2.1 storage_list.xml的動態配置
在雙U盤策略下,由於需要動態的切換主存取器,就需要在對storage_list.xml的能相對應的做動態的解析。
storage_list.xml的配置參數說明如下:
參數 |
值類型 |
說明 |
mountPoint |
String |
[p4] 掛載點,同Vold.fstab里的掛載點對應 |
storageDescription |
String |
掛載點描述符 |
Primary |
Boolean |
該磁盤是否為主存儲器 |
Removable |
Boolean |
該磁盤是否可移除,外置SD卡為可移除,內置空間不可移除 |
Emulated |
Boolean |
該磁盤是否為模擬磁盤,外置SD卡及獨立udisk為非模擬磁盤,Android原生sdcard service啟動的磁盤為模擬磁盤[p5] |
allowMassStorage |
Boolean |
該磁盤是否可用作大容量存儲 |
MtpReserve |
Integer |
在MTP協議里保留的磁盤可用空間(MB單位) |
storage_list.xml的配置參數說明圖
在原生的Mountservice里,對storage_list.xml為靜態配置,即版本編譯完后,storage_list.xml的值就決定了所有磁盤的形態,尤其是對主存儲器掛載點是不可修改的。在雙U盤設計里,就創新的使用了可配置的解析storage_list.xml,使用多組配置數據,來決定不同情況下,主存儲器的掛載路徑。目前使用的storage_list.xml配置如下:
參數 |
值類型 |
值 |
mountPoint |
String |
/storage/sdcard0 |
storageDescription |
String |
@string/storage_internal |
Primary |
Boolean |
true |
Removable |
Boolean |
false |
Emulated |
Integer |
false |
allowMassStorage |
Boolean |
true |
MtpReserve |
String |
100 |
主存儲器為內置U盤時的內置U盤配置
參數 |
值類型 |
值 |
mountPoint |
String |
/storage/sdcard1 |
storageDescription |
String |
@string/storage_sd_card |
Primary |
Boolean |
false |
Removable |
Boolean |
true |
Emulated |
Integer |
false |
allowMassStorage |
Boolean |
true |
MtpReserve |
String |
100 |
主存儲器為內置U盤時的外置SD卡配置
值類型 |
值 |
|
mountPoint |
String |
/storage/sdcard1 |
storageDescription |
String |
@string/storage_internal |
Primary |
Boolean |
false |
Removable |
Boolean |
false |
Emulated |
Integer |
false |
allowMassStorage |
Boolean |
|
MtpReserve |
String |
100 |
主存儲器為外置SD卡時的內置U盤配置
參數 |
值類型 |
值 |
mountPoint |
String |
/storage/sdcard0 |
storageDescription |
String |
@string/storage_sd_card |
Primary |
Boolean |
|
Removable |
Boolean |
true |
Emulated |
Integer |
false |
allowMassStorage |
Boolean |
true |
MtpReserve |
String |
100 |
主存儲器為外置SD卡時的外置SD卡配置
而在Mountservice里,就需要根據用戶當前設置的主存儲器的不同,而決定解析使用哪一組的storage_list.xml,從而實現了在Framework層的動態切換主存儲器功能。
2.8.2.2 Mountservice添加對多個大容量存儲設備的支持
Android Framework的原生設計里,只支持一塊磁盤可以作為大容量存儲,也就是說在storage_list.xml里只有一個磁盤卷設置了allowMassStorage為TRUE,而在雙U盤的設計里,需要支持兩個大容量存儲器,同kernel的修改一樣,Framework的Mountservice里,也需要增加對多個UMS的支持。
在原生的Mountservice里,獲取UMS的方法為調用接口:
/**
* get the path of the ums device mounted at.
* there is a little hack, we always has onlyone device used ums.
*/
private String getUmsPath() {
synchronized (mVolumesLock) {
for (StorageVolume v : mVolumes) {
if ( v.allowMassStorage()) {
return v.getPath() ;
}
}
}
return null;
}
對應的需要修改該接口為返回一個String的List,可以包含多個UMS磁盤[p9] :
private List<String>getUmsPath() {
List<String> list = newArrayList<String>();
synchronized (mVolumesLock) {
for (StorageVolume v : mVolumes) {
if ( v.allowMassStorage()) {
list.add(v.getPath());
}
}
}
return list;
}
同時,在Mountservice里所有使用到UMS磁盤卷的地方,都需要相應的將原來只返回一個UMS卷的方法修改為返回一組StringList的UMS卷組。
而在切換大容量存儲的操作時,Mountservice對上層和下層的指令切換不做更改,保持原生的切換大容量存儲的指令流程。在雙U盤方案里,每次切換都是整個UMS的list同時切換,保證了功能完整性和可維護性。
2.8.3雙U盤功能在Framework的功能宏控制
//TODO
1.1 APP層
在應用層,Android SDK會提供Environment的一系列接口用於訪問外部存儲空間。其中最主要的接口getExternalStorageDirectory()和getPrimaryVolume()均為返回默認的主存儲[p10] 器的掛載路徑。在雙U盤的設計里,能夠實現默認的主存儲器對上層是透明的,也就是說,所有底層的修改,雙U盤的功能添加,都不會對上層應用調用SDK接口有影響,因此也保證了第三方應用能正常使用Android SDK的接口,保證平台的穩定性。
當然,由於添加了一個物理上的磁盤,在Settings應用里也會同步增加一個內部存儲的磁盤卷,並且由於雙U盤設計能動態切換主存儲器掛載點,也需要在Settings應用做相應的功能添加。
在Setting的存儲模塊,添加對默認存儲分區的控制和設置的UI界面。
默認存儲器的設置值保存在/data/etc/storage.config下,在第一次燒錄版本的時候,需要默認自動生成該文件並且保證有默認值,所以需要在編譯時就默認生成該文件。Android編譯框架下的device.mk里的全局PRODUCT_COPY_FILES控制編譯時文件的復制,因此需要添加如下的文件:
#storageconfig
PRODUCT_COPY_FILES+= \
device/leadcore/ust802p0/storage.config:data/etc/storage.config
Settings需要對此文件可以讀寫,而默認生成的該文件由於是編譯時生成,其文件的屬性為root,而Settings應用的屬性為System,對此文件無法讀寫,因此需要在系統啟動后,對此文件的屬性進行修改。修改文件屬性的方法為在init.rc添加修改腳本:
chownsystem system /data/etc/storage.config
至此,Settings應用對默認存儲器的讀寫控制可以實現,而/data/etc/storage.config也可以提供給vold,指明當前使用哪個掛載點為默認存儲器。