0%

[TOC]

1. 文件系统的组成部分

1.1. block块理解

硬盘的读写IO一次是一个扇区512字节, 如果要读写大量文件,以扇区为单位肯定很慢很消耗性能,所以Linux中通过文件系统控制使用"块"为读写单元
比如需要读一个或多个块时,文件系统的IO管理器通知磁盘控制器要读取哪些的数据,硬盘控制器将这些块按扇区读取出来,再通过硬盘控制器将这些扇区数据重组返回给计算机

  • block的出现使得在文件系统层面上读写性能大大提高,也大量减少了碎片
  • 可能造成空间浪费

    在当下硬盘容量廉价且追求性能的时代,使用block是一定的。

1.2. inode

假如block大小为1KB,仅仅存储一个10M的文件就需要10240个block,而且这些blocks很可能在位置上是不连续在一起的(不相邻)

读取该文件时难道要从前向后扫描整个文件系统的块,然后找出属于该文件的块吗?
???
每个文件都有属性(如权限、大小、时间戳等),这些属性类的元数据存储在哪里呢?难道也和文件的数据部分存储在块中吗?
如果一个文件占用多个block那是不是每个属于该文件的block都要存储一份文件元数据?
如果不在每个block中存储元数据文件系统又怎么知道某一个block是不是属于该文件呢?
解决方法是使用索引,通过扫描索引找到对应的数据,而且索引可以存储部分数据
在文件系统上索引技术具体化为索引节点(index node),在索引节点上存储的部分数据即为文件的属性元数据及其他少量信息.
索引节点称为inode。在inode中存储了inode号、文件类型、权限、文件所有者、大小、时间戳等元数据信息,最重要的是还存储了指向属于该文件block的指针

  • 可以用stat命令,查看某个文件的inode信息:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    $ stat ~/.bashrc         
    File: ‘/home/local/SPREADTRUM/liguang.zhang/.bashrc’
    Size: 7829 Blocks: 16 IO Block: 4096 regular file
    Device: 821h/2081d Inode: 39059515 Links: 1
    Access: (0644/-rw-r--r--) Uid: (1488486870/SPREADTRUM\liguang.zhang) Gid: (1488454145/SPREADTRUM\domain^users)
    Access: 2019-03-18 09:52:16.796663821 +0800
    Modify: 2019-02-22 10:10:58.044537613 +0800
    Change: 2019-02-22 10:10:58.044537613 +0800
    Birth: -
  • 查看每个inode节点的大小,可以用如下命令:
    1
    2
    3
    $ sudo dumpe2fs /dev/sda1 | grep "Inode size"
    dumpe2fs 1.42.9 (4-Feb-2014)
    Inode size: 256
    打印文件的inode number
    ls命令用于列出文件/文件夹的信息。参数 -i 说明需要显示每个文件的inode number。我们可以结合参数 -l 一起使用以列出详细信息:
    1
    2
    3
    4
    $ ls -li
    total 16
    43650565 drwxr-xr-x 108 SPREADTRUM\liguang.zhang SPREADTRUM\domain^users 12288 Mar 15 10:10 extensions
    43783364 drwxr-xr-x 2 SPREADTRUM\liguang.zhang SPREADTRUM\domain^users 4096 May 17 2018 projects
    显示文件系统inode的使用信息
    df命令汇总可用和已用的磁盘空间。你可以通过传递 -i 或 –inodes 选项来接收有关可用和已使用的inode报告。
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    $ df -i             
    Filesystem Inodes IUsed IFree IUse% Mounted on
    udev 2040313 563 2039750 1% /dev
    tmpfs 2043070 647 2042423 1% /run
    /dev/sda1 3055616 909414 2146202 30% /
    none 2043070 2 2043068 1% /sys/fs/cgroup
    none 2043070 5 2043065 1% /run/lock
    none 2043070 119 2042951 1% /run/shm
    none 2043070 38 2043032 1% /run/user
    /dev/sda3 25526272 276893 25249379 2% /home
    /dev/sdb3 56901632 6017447 50884185 11% /home/newdisk
    /dev/sdc1 244195328 7604192 236591136 4% /home/newdisk1
    //tjnas1/data_exchange_tunnel/From_Shanghai/PLD_APPS/liguang.zhang 0 0 0 - /home/newdisk1/liguang.zhang/smb_dir/datasync
    列出文件系统超级块的信息
    tune2fs -l 命令来显示所有与inode相关的信息
    1
    2
    3
    4
    5
    6
    7
    $ sudo tune2fs -l /dev/sda1 | grep inode
    Filesystem features: has_journal ext_attr resize_inode dir_index filetype needs_recovery extent flex_bg sparse_super large_file huge_file uninit_bg dir_nlink extra_isize
    Free inodes: 2146270
    First inode: 11
    Journal inode: 8
    First orphan inode: 131185
    Journal backup: inode blocks

1.3. bmap出现(只对写优化)

阅读全文 »

1. 文件系统知识1

1.1. 文件相关数据结构

每个进程的task_struct结构体中, 包含一个struct files_struct* files结构体指针,指向该进程所有已经打开的文件/目录对应的相关信息的结构体.
files内部又包含struct file fd_array[]数组, 每一个都是struct file结构体(又被称作fd, 文件描述符), 每个file结构体中记录了被打开文件的基本信息, 如记录该文件对应的目录项dentry,
用于操作该文件的file_operations回调函数等, 从dentry中可以找到该文件对应的inode结构体, 从而得到该文件的各种属性信息.

fd_array数组的长度存放在max_fds字段中, linux系统中,一个进程打开的文件数是有初步限制的,即其文件描述符数初始时有最大化定量,即一个进程一般只能打开NR_OPEN_DEFAULT个文件,该值在32位机上为32个,在64位机上为64个
init fork一个子进程时, 也是如此.

1
2
3
do_fork======->copy_process
|======->dup_task_struct======->alloc_task_struct
|======->copy_files======->dup_fd======->alloc_files

1.1.1. struct files_struct扩充

进程打开的fd数目超过NR_OPEN_DEFAULT时, 对已经初始化的files_struct进行扩充
当进行struct files_struct扩充时,会分配一个新的struct fdtable, 分配并初始化新的struct fdtable变量后,原先指向fdtab的struct files_struct指针成员fdt,会指向新分配的struct fdtable变量。这时,struct files_struct实例变量中就包含两个struct fdtable存储区:一个是其自身的,一个新分配的,用fdt指向。执行完上述的操作后,还要将旧的结构存储区的内容拷贝到新的存储区,这包括files_struct自身所包含的close_on_exec,open_fds,fd到新分配的close_on_exec,open_fds,fd的拷贝。
执行完上述拷贝之后,就要释放旧的struct fdtable,但这里并不执行执行该项释放操作. (需要时机触发)
struct files_struct扩充使用内核源码中的expand_files来实现,expand_files会调用expand_fdtable

1
2
3
4
5
6
7
8
9
10
11
12
13
14
static int expand_fdtable(struct files_struct *files, int nr)
{
struct fdtable *new_fdt, *cur_fdt;
new_fdt = alloc_fdtable(nr); //分配了一个fdtable
cur_fdt = files_fdtable(files); //files->fdt
if (nr >= cur_fdt->max_fds) {
/* Continue as planned */
copy_fdtable(new_fdt, cur_fdt); //拷贝了其中的3个变量:fd,open_fds,close_on_exec
rcu_assign_pointer(files->fdt, new_fdt); //将新分配的fdtable赋值给files的fdt
if (cur_fdt->max_fds > NR_OPEN_DEFAULT) //注意它第一次初始化为NR_OPEN_DEFAULT
free_fdtable(cur_fdt);
}
return 1;
}

扩充后, 内核会同时更新max_fds字段值.

files_struct扩充

阅读全文 »

1. debugfs 简介

对于ext2/ext3/ext4 系列文件系统, 可以使用debugfs来跟踪文件系统的相关信息

首先看下常见的命令

  • stats 查看文件系统super block的数据, 类似dumpe2fs的功能
  • icheck 根据提供的block块号反查其inode号
    1
    2
    3
    debugfs:  icheck 13440000
    Block Inode number
    13440000 3358728
  • ncheck 根据提供的inode 号反查其文件名, 文件名的信息存放在目录项的data block中
    1
    2
    3
    debugfs:  ncheck 3358728
    Inode Pathname
    3358728 /unencrypted/mode
  • stat 根据提供的文件路径查看文件的综合信息, 比在本地用提供的信息更多, 包括extents段也给出了
1
2
3
4
stat unencrypted
Inode: 3358728 Type: regular Mode: 0600 Flags: 0x80000
EXTENTS:
(0):13435394
  • bd 打印块的内容
    1
    2
    3
    4
    5
    6
    7
    8
    9
    debugfs:  bd 13435394
    # 目录下文件名和inode号的映射, 对应ext4_dir_entry_2结构
    0000 0140 3300 0c00 0102 2e00 0000 0200 0000 .@3.............
    0020 0c00 0202 2e2e 0000 0840 3300 0c00 0401 .........@3.....
    0040 6d6f 6465 0240 3300 0c00 0302 6b65 7900 mode.@3.....key.
    0060 0940 3300 d00f 0301 7265 6600 0000 0000 .@3.....ref.....
    0100 0000 0000 0000 0000 0000 0000 0000 0000 ................
    # ext4_dir_entry_2 entry总长在rec_len字段, 文件名长度在name_len字段.
    0840 3300 0x334008 = 3358728 正好是mode的inode号
    dir block
  • filefrag 打印当前目录文件系统碎片情况
    1
    2
    3
    4
    debugfs:  filefrag
    ./mode: 1 contiguous extents
    ./key: 1 contiguous extents (dir)
    ./ref: 1 contiguous extents
  • testb 查询给定的块号是否已被使用 Test a block’s in-use flag
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    debugfs:  testb 20 20
    Block 20 marked in use
    Block 21 marked in use
    Block 22 marked in use
    Block 23 marked in use
    Block 24 marked in use
    Block 25 marked in use
    Block 26 marked in use
    Block 27 marked in use
    Block 28 marked in use
    Block 29 marked in use
    Block 30 marked in use
    Block 31 marked in use
    Block 32 marked in use
    Block 33 marked in use
    Block 34 marked in use
    Block 35 marked in use
    Block 36 marked in use
    Block 37 marked in use
    Block 38 marked in use
    Block 39 marked in use
  • ffi 查询下一个可用的inode号是多少, 下一次创建文件时会使用该inode号
  • freefrag/e2freefrag 查询free extent的直方图
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    HISTOGRAM OF FREE EXTENT SIZES:
    Extent Size Range : Free extents Free Blocks Percent
    4K... 8K- : 85 85 0.00%
    8K... 16K- : 119 301 0.00%
    16K... 32K- : 230 1218 0.01%
    32K... 64K- : 260 2814 0.02%
    64K... 128K- : 259 5864 0.05%
    128K... 256K- : 227 10680 0.09%
    256K... 512K- : 157 14647 0.12%
    512K... 1024K- : 114 20521 0.16%
    1M... 2M- : 119 44132 0.35%
    2M... 4M- : 51 35681 0.29%
    4M... 8M- : 59 84734 0.68%
    8M... 16M- : 12 33740 0.27%
    16M... 32M- : 24 144008 1.15%
    32M... 64M- : 14 162912 1.31%
    64M... 128M- : 360 11392885 91.32%
  • id . dump inode号块
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    #id test
    0000 b681 0000 0200 0000 3e60 045e 3e60 045e ........>`.^>`.^
    0020 3e60 045e 0000 0000 0000 0100 0800 0000 >`.^............
    0040 0000 0800 0100 0000 0af3 0100 0400 0000 ................
    0060 0000 0000 0000 0000 0100 0000 4d08 0000 ............M...
    0100 0000 0000 0000 0000 0000 0000 0000 0000 ................
    *
    0140 0000 0000 6752 620f 0000 0000 0000 0000 ....gRb.........
    0160 0000 0000 0000 0000 0000 0000 0000 0000 ................
    0200 2000 0000 3458 ea2a 3458 ea2a 3458 ea2a ...4X.*4X.*4X.*
    0220 3e60 045e 3458 ea2a 0000 0000 0000 0000 >`.^4X.*........
    0240 0000 02ea 0706 3c00 0000 0000 1f00 0000 ......<.........
    0260 0000 0000 7365 6c69 6e75 7800 0000 0000 ....selinux.....
    0300 0000 0000 0000 0000 0000 0000 0000 0000 ................
    *
    0340 753a 6f62 6a65 6374 5f72 3a73 7973 7465 u:object_r:syste
    0360 6d5f 6461 7461 5f66 696c 653a 7330 0000 m_data_file:s0..
阅读全文 »

1. 开启Midi function

在Usb模式中切换为midi模式, 跟切换其他模式一样, 如切换mtp, ptp等
sys.usb.config = midi

收到底层的uevent, 在MSG_UPDATE_STATE中进行处理
updateUsbFunctions扫描/sys/class/android_usb/android0/f_midi/alsa下文件, 调用UsbAlsaManager的setPeripheralMidiState接口添加音频设备.

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
        private void updateMidiFunction() {
boolean enabled = (mCurrentFunctions & UsbManager.FUNCTION_MIDI) != 0;
if (enabled != mMidiEnabled) {
// 打开midi function
if (enabled) {
Scanner scanner = null;
try {
// 扫描"/sys/class/android_usb/android0/f_midi/alsa"
scanner = new Scanner(new File(MIDI_ALSA_PATH));
// 打开midi模式后, 会生成alsa的声卡, 添加card device号
mMidiCard = scanner.nextInt();
mMidiDevice = scanner.nextInt();
}
finally {
if (scanner != null) {
scanner.close();
}
}
}
mMidiEnabled = enabled;
}
1. mUsbAlsaManager.setPeripheralMidiState(
mMidiEnabled && mConfigured, mMidiCard, mMidiDevice);
}


/* package */ void setPeripheralMidiState(boolean enabled, int card, int device) {
if (!mHasMidiFeature) {
return;
}

if (enabled && mPeripheralMidiDevice == null) {
Bundle properties = new Bundle();
Resources r = mContext.getResources();
properties.putString(MidiDeviceInfo.PROPERTY_NAME, r.getString(
com.android.internal.R.string.usb_midi_peripheral_name));
properties.putString(MidiDeviceInfo.PROPERTY_MANUFACTURER, r.getString(
com.android.internal.R.string.usb_midi_peripheral_manufacturer_name));
properties.putString(MidiDeviceInfo.PROPERTY_PRODUCT, r.getString(
com.android.internal.R.string.usb_midi_peripheral_product_name));
properties.putInt(MidiDeviceInfo.PROPERTY_ALSA_CARD, card);
properties.putInt(MidiDeviceInfo.PROPERTY_ALSA_DEVICE, device);
1.->// 创建UsbMidiDevice
mPeripheralMidiDevice = UsbMidiDevice.create(mContext, properties, card, device);
}
}

public static UsbMidiDevice create(Context context, Bundle properties, int card, int device) {
// FIXME - support devices with different number of input and output ports
int subDeviceCount = nativeGetSubdeviceCount(card, device);
1.1 // 构造UsbMidiDevice
UsbMidiDevice midiDevice = new UsbMidiDevice(card, device, subDeviceCount);
1.2 // 注册midi服务
if (!midiDevice.register(context, properties)) {
}
return midiDevice;
}

1.1->// 创建 InputReceiverProxy mInputPortReceivers 以
snprintf(path, sizeof(path), "/dev/snd/controlC%d", card);
// 下的端口inputPort状态创建, 通信端

private UsbMidiDevice(int card, int device, int subdeviceCount) {
mAlsaCard = card;
mAlsaDevice = device;
mSubdeviceCount = subdeviceCount;

// FIXME - support devices with different number of input and output ports
int inputPortCount = subdeviceCount;

mInputPortReceivers = new InputReceiverProxy[inputPortCount];
for (int port = 0; port < inputPortCount; port++) {
mInputPortReceivers[port] = new InputReceiverProxy();
}
}
1.2 ->// 启动MidiDeviceServer

private boolean register(Context context, Bundle properties) {
MidiManager midiManager = (MidiManager)context.getSystemService(Context.MIDI_SERVICE);
if (midiManager == null) {
Log.e(TAG, "No MidiManager in UsbMidiDevice.create()");
return false;
}
1.2.1 // 构造MidiDeviceServer
mServer = midiManager.createDeviceServer(mInputPortReceivers, mSubdeviceCount,
null, null, properties, MidiDeviceInfo.TYPE_USB, mCallback);
if (mServer == null) {
return false;
}

return true;
}


public MidiDeviceServer createDeviceServer(MidiReceiver[] inputPortReceivers,
int numOutputPorts, String[] inputPortNames, String[] outputPortNames,
Bundle properties, int type, MidiDeviceServer.Callback callback) {
try {
1.2.1.1 // 创建MidiDeviceServer
MidiDeviceServer server = new MidiDeviceServer(mService, inputPortReceivers,
numOutputPorts, callback);
1.2.1.2 // 调用MidiService的registerDeviceServer, add device
MidiDeviceInfo deviceInfo = mService.registerDeviceServer(server.getBinderInterface(),
inputPortReceivers.length, numOutputPorts, inputPortNames, outputPortNames,
properties, type);
if (deviceInfo == null) {
Log.e(TAG, "registerVirtualDevice failed");
return null;
}
return server;
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
}

1.2.1.1 -> //初始化MidiDeviceServer, callback对象为MidiDeviceServer.Callback, 回调其onDeviceStatusChanged方法

// Constructor for MidiManager.createDeviceServer()
/* package */ MidiDeviceServer(IMidiManager midiManager, MidiReceiver[] inputPortReceivers,
int numOutputPorts, Callback callback) {
mMidiManager = midiManager;
mInputPortReceivers = inputPortReceivers;
mInputPortCount = inputPortReceivers.length;
mOutputPortCount = numOutputPorts;
mCallback = callback;

// 注意 mInputPortReceivers mInputPortOutputPorts mOutputPortDispatchers 这三个成员, 后面会用到数据传输
mInputPortOutputPorts = new MidiOutputPort[mInputPortCount];

mOutputPortDispatchers = new MidiDispatcher[numOutputPorts];
for (int i = 0; i < numOutputPorts; i++) {
mOutputPortDispatchers[i] = new MidiDispatcher(mInputPortFailureHandler);
}

mInputPortOpen = new boolean[mInputPortCount];
mOutputPortOpenCount = new int[numOutputPorts];

mGuard.open("close");
}

1.2.1.2-> // 注册DeviceServer,

public MidiDeviceInfo registerDeviceServer(IMidiDeviceServer server, int numInputPorts,
int numOutputPorts, String[] inputPortNames, String[] outputPortNames,
Bundle properties, int type) {
// 权限检查, 非系统用户不能添加
int uid = Binder.getCallingUid();
if (type == MidiDeviceInfo.TYPE_USB && uid != Process.SYSTEM_UID) {
throw new SecurityException("only system can create USB devices");
} else if (type == MidiDeviceInfo.TYPE_BLUETOOTH && uid != mBluetoothServiceUid) {
throw new SecurityException("only MidiBluetoothService can create Bluetooth devices");
}

//创建MidiDeviceInfo, 并设置到MidiDeviceServer中, 最后将device保存到MidiService的mDevicesByInfo中.
synchronized (mDevicesByInfo) {
return addDeviceLocked(type, numInputPorts, numOutputPorts, inputPortNames,
outputPortNames, properties, server, null, false, uid);
}
}

上述步骤为切换USb function为midi后, 进行的初始化步骤, 其中最重要的是创建了UsbMidiDevice, 启动了UsbMidiServer服务.关联的驱动节点为
alsa架构的节点/dev/snd/controlC%d

2. App使用MidiService服务

先来看一段demo code
服务启动后, app需要通过mMidiManager.registerDeviceCallback注册回调.
通过MidiManager的getDevices获取当前的UsbMidiDevice设备.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public MidiPortSelector(MidiManager midiManager, Activity activity,
int spinnerId, int type) {
//客户端为MidiManager, MidiManager的对端为MidiService, MidiService为一开机就加载的服务
mMidiManager = midiManager;
mActivity = activity;
mMidiManager.registerDeviceCallback(this,
new Handler(Looper.getMainLooper()));
// getDevice
MidiDeviceInfo[] infos = mMidiManager.getDevices();
for (MidiDeviceInfo info : infos) {
// 查到后, 保存到app的内部结构中, (此处关联到ui中)
onDeviceAdded(info);
}
}

public void onDeviceAdded(final MidiDeviceInfo info) {
int portCount = getInfoPortCount(info);
for (int i = 0; i < portCount; ++i) {
MidiPortWrapper wrapper = new MidiPortWrapper(info, mType, i);
mAdapter.add(wrapper);
}
}

通过registerDeviceCallback注册后, 在MidiService中保存, 这一步主要用到的数据成员都在1.2.1.2步骤中. 注册后, 会紧接着调用updateStickyDeviceStatus通知到客户端
onDeviceStatusChanged

1
2
3
4
5
6
7
8
public void registerDeviceCallback(DeviceCallback callback, Handler handler) {
DeviceListener deviceListener = new DeviceListener(callback, handler);
try {
mService.registerListener(mToken, deviceListener);
}
// callback最终保存到mDeviceListeners中
mDeviceListeners.put(callback, deviceListener);
}
阅读全文 »

1. Device变化

1.1. adb重构

将adb调试相关的类抽出到新包com.android.server.adb中.

  • 包名变化
    com.android.server.usb ->com.android.server.adb
  • 目录文件变更
  • /frameworks/base/services/usb/java/com/android/server/usb ->
    /frameworks/base/services/core/java/com/android/server/adb

UsbDebuggingManager.java ->AdbDebuggingManager.java
从UsbService中抽出AdbService.java

原有的UsbDebuggingManager变为AdbDebuggingManager
新增: /frameworks/base/services/core/java/com/android/server/adb

  1. AdbService –和UsbService并列, 由SystemServer启动
  2. 交互方式变更, 通过LocalService的AdbManagerInternal registerTransport注册adb监听.
  3. adbService中adb开关的变化通过contentProvider监听, 而其他服务通过registerTransport监听adbService中的adb开关变化回调.
    1
    2
    LocalServices.getService(
    AdbManagerInternal.class).registerTransport(new AdbTransport(this));

1.2. 新增adb权限

1
2
3
4
<permission android:name="android.permission.MANAGE_DEBUGGING"
android:protectionLevel="signature|privileged" />
<!-- Permission to control Android Debug Bridge (ADB) -->
<uses-permission android:name="android.permission.MANAGE_DEBUGGING" />

allowDebugging denyDebugging clearDebuggingKeys方法中会鉴权, 还是UsbDeviceManager中操作.

阅读全文 »

结构

  • UsbPortManager

  • UsbHostManager

    • UsbAlsaManager
  • UsbDeviceManager

    usb//images/usb2.png

1. UsbPortManager-usb role管理

1.1. USB主机和USB设备

USB通信中居于核心地位的是主机(Host),常见的USB主机是PC机。

  • 任何一次USB的数据传输都必须由主机发起和控制

  • 所有的USB外设都只能和主机建立连接

  • 任何二个外设之间或是二个主机之间都无法直接通信

USB主机和USB设备的功能是不同的。

USB主机的功能:

  • 通过USB接口给外设提供电源(外设也可以自带电源)
  • 检测和配置设备(即设备的枚举)
  • 错误检查和管理数据的传输
  • 根据设定的传输方式与外设交换数据
阅读全文 »

1. 开篇

本篇主要从存储挂载系统介绍fuse的相关流程. 在Android系统中, 在内部存储和外部存储之上新加了fuse 挂载, 对内部存储而言, 多用户情况下, 每个用户只能访问自己的内部存储目录(对应/mnt/runtime/read|write/emulated目录.) , 除此之外, 对应运行时权限的需求, 非特权应用(没有平台签名 , 不能申请安装权限android.permission.WRITE_MEDIA_STORAGE 的应用, 只能通过申请运行时WRITE_EXTERNAL_STORAGE权限的应用) 通过fuse实现了对存储的访问需求.

2. 从sdcard volume挂载说起

/system/bin/sdcard 进程为fuse的用户态守护进程. 在sd卡的volume卷挂载后, 会执行下列命令

1
2
3
4
5
6
7
8
9
10
11
/system/bin/sdcard -u 1023 -g 1023 -U userid -w /mnt/media_rw/XXXX-XXXX XXXX-XXXX
if (execl(kFusePath, kFusePath,
"-u", "1023", // AID_MEDIA_RW
"-g", "1023", // AID_MEDIA_RW
"-U", std::to_string(getMountUserId()).c_str(),
"-w",
mRawPath.c_str(),
stableName.c_str(),
NULL)) {
PLOG(ERROR) << "Failed to exec";
}

进入sdcard进程, uid 1023 gid 1023 userid (当前的用户id) -w full_write mRawPath对应 source_path, label 对应 stableName

没有配置sdcardfs情况下,

1
2
3
4
5
6
7
8
9
// 对于sd卡, multi_user参数没传, 这里为false. 对于内部存储, multi_user为true
run(source_path, label, uid, gid, userid, multi_user, full_write);
/* Physical storage is readable by all users on device, but
* the Android directories are masked off to a single user
* deep inside attr_from_stat(). */
// full_write 为 true
fuse_setup(&fuse_default, AID_SDCARD_RW, 0006)
|| fuse_setup(&fuse_read, AID_EVERYBODY, full_write ? 0027 : 0022)
|| fuse_setup(&fuse_write, AID_EVERYBODY, full_write ? 0007 : 0022))

指定了fuse_default/fuse_read/fuse_write 结构体挂载 /mnt/runtime/default | read | write.

对于前面的参数, 大多保存到了 struct fuse_global global 字段中. 初始化根目录node,sdcard也类似kernel fs为每个目录和文件维护了一个node结构体

阅读全文 »

Android 4.4(API 级别 19)引入了存储访问框架 (SAF)。它可以让用户方便的浏览、打开文档、图片和其它
的文件。用户可以通过标准的用户界面来浏览文件、打开最近访问的文件。

SAF框架包含下面三个部分:

  • Document provider:

    一种ContentProvider,DocumentsProvider的子类。Android平台内置了多个,例如Downloads,Images,Videos

  • Client App:

      应用端,调用`ACTION_OPEN_DOCUMENT`/`ACTION_CREATE_DOCUMENT`,并且接收provider返回的文件
    
  • Picker:

    系统界面,允许用户访问所有满足应用端搜索条件的provider提供的文档。例如平台的DocumentsUI

SAF 提供的部分功能如下:

  • 允许用户浏览所有文档提供程序而不仅仅是单个应用中的内容;
  • 让应用获得对provider所拥有文档的长期、持久性访问权限。 用户可以通过此访问权限添加、编辑、保存
    和删除提供程序上的文件;
  • 支持多个用户帐户和临时根目录,如只有在插入驱动器后才会出现的 USB 存储提供程序。
    注意:
    在SAF中,客户端并不会直接与provider交互。客户端请求与文件交互(即读、编辑、创建、删除)的权限。

1. 应用端代码示例

1.1. 授予目录访问权限

1.1.1. 通过ACTION_OPEN_DOCUMENT_TREE授予应用访问特定目录的权限

1
2
3
4
5
public void performGrantSdWriteAcess() {
Intent intent = new Intent(Intent.ACTION_OPEN_DOCUMENT_TREE);
intent.addCategory(Intent.CATEGORY_DEFAULT);
startActivityForResult(intent, TREE_REQUEST_CODE);
}

通过DocumentsUi picker 选择好目录后,可以在onActivityResult中对返回的URI进行处理

阅读全文 »

1. VFS

VFS以一组通用对象看待所有文件系统.

  • 超级块(SuperBlock)
  • 索引节点(inode)
  • 目录项(dentry)
  • 文件(struct file)

1.1. 超级块 sb

超级块代表一个已经安装的文件系统,存储该文件系统的有关信息(如类型\大小\状态等)

  1. 对基于磁盘的文件系统, 该对象通常存放在磁盘的特定扇区上;
  2. 非磁盘的(如sysfs),现场创建保存在内存中
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
struct super_block {
//用于形成超级块链表
struct list_head s_list; /* Keep this first */
// 所属文件系统所在的设备描述符
dev_t s_dev; /* search index; _not_ kdev_t */
// 一个块需要几个bit表示, 如块为1024字节, 该处就是10 块大小的就位数
unsigned char s_blocksize_bits;
// 块大小, 以字节为单位.
unsigned long s_blocksize;
loff_t s_maxbytes; /* Max file size */
// 文件系统类型
struct file_system_type *s_type;
// 超级块操作
const struct super_operations *s_op;
// VFS磁盘限额处理方法
const struct dquot_operations *dq_op;
// 用于配置磁盘限额的方法,处理来自用户空间的请求
const struct quotactl_ops *s_qcop;
// 导出的方法, 从NFS服务器中共享目录又称导出目录
const struct export_operations *s_export_op;
// mount的flag,
unsigned long s_flags;
unsigned long s_iflags; /* internal SB_I_* flags */
// 魔数,用于识别文件系统
unsigned long s_magic;
// 文件系统的根目录的目录项对象, 从文件系统的超级块可以读到任何一个文件, 前提是知道根目录在哪里.
struct dentry *s_root;
// 卸载所用的信号量
struct rw_semaphore s_umount;
// 引用计数
int s_count;
// 引用计数
atomic_t s_active;
// 指向超级块扩展属性结构的指针
const struct xattr_handler **s_xattr;
// 加密相关的操作
const struct fscrypt_operations *s_cop;
// 要导出的匿名目录项的列表
struct hlist_bl_head s_anon; /* anonymous dentries for (nfs) exporting */
struct list_head s_mounts; /* list of mounts; _not_ for fs use */
struct block_device *s_bdev;
struct backing_dev_info *s_bdi;
struct mtd_info *s_mtd;
struct hlist_node s_instances;
unsigned int s_quota_types; /* Bitmask of supported quota types */
struct quota_info s_dquot; /* Diskquota specific options */

struct sb_writers s_writers;

char s_id[32]; /* Informational name */
u8 s_uuid[16]; /* UUID */

void *s_fs_info; /* Filesystem private info */
unsigned int s_max_links;
fmode_t s_mode;

/* Granularity of c/m/atime in ns.
Cannot be worse than a second */
u32 s_time_gran;

/*
* The next field is for VFS *only*. No filesystems have any business
* even looking at it. You had been warned.
*/
struct mutex s_vfs_rename_mutex; /* Kludge */

/*
* Filesystem subtype. If non-empty the filesystem type field
* in /proc/mounts will be "type.subtype"
*/
char *s_subtype;

/*
* Saved mount options for lazy filesystems using
* generic_show_options()
*/
char __rcu *s_options;
const struct dentry_operations *s_d_op; /* default d_op for dentries */

/*
* Saved pool identifier for cleancache (-1 means none)
*/
int cleancache_poolid;

struct shrinker s_shrink; /* per-sb shrinker handle */

/* Number of inodes with nlink == 0 but still referenced */
atomic_long_t s_remove_count;

/* Being remounted read-only */
int s_readonly_remount;

/* AIO completions deferred from interrupt context */
struct workqueue_struct *s_dio_done_wq;
struct hlist_head s_pins;

/*
* Keep the lru lists last in the structure so they always sit on their
* own individual cachelines.
*/
struct list_lru s_dentry_lru ____cacheline_aligned_in_smp;
struct list_lru s_inode_lru ____cacheline_aligned_in_smp;
struct rcu_head rcu;
struct work_struct destroy_work;

struct mutex s_sync_lock; /* sync serialisation lock */

/*
* Indicates how deep in a filesystem stack this SB is
*/
int s_stack_depth;

/* s_inode_list_lock protects s_inodes */
spinlock_t s_inode_list_lock ____cacheline_aligned_in_smp;
struct list_head s_inodes; /* all inodes */
};

1.1.1. 超级块操作

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
struct super_operations {
struct inode *(*alloc_inode)(struct super_block *sb);
void (*destroy_inode)(struct inode *);
void (*dirty_inode) (struct inode *, int flags);
int (*write_inode) (struct inode *, struct writeback_control *wbc);
int (*drop_inode) (struct inode *);
void (*evict_inode) (struct inode *);
void (*put_super) (struct super_block *);
int (*sync_fs)(struct super_block *sb, int wait);
int (*freeze_super) (struct super_block *);
int (*freeze_fs) (struct super_block *);
int (*thaw_super) (struct super_block *);
int (*unfreeze_fs) (struct super_block *);
int (*statfs) (struct dentry *, struct kstatfs *);
int (*remount_fs) (struct super_block *, int *, char *);
int (*remount_fs2) (struct vfsmount *, struct super_block *, int *, char *);
void *(*clone_mnt_data) (void *);
void (*copy_mnt_data) (void *, void *);
void (*umount_begin) (struct super_block *);

int (*show_options)(struct seq_file *, struct dentry *);
int (*show_options2)(struct vfsmount *,struct seq_file *, struct dentry *);
int (*show_devname)(struct seq_file *, struct dentry *);
int (*show_path)(struct seq_file *, struct dentry *);
int (*show_stats)(struct seq_file *, struct dentry *);
#ifdef CONFIG_QUOTA
ssize_t (*quota_read)(struct super_block *, int, char *, size_t, loff_t);
ssize_t (*quota_write)(struct super_block *, int, const char *, size_t, loff_t);
struct dquot **(*get_dquots)(struct inode *);
#endif
int (*bdev_try_to_free_page)(struct super_block*, struct page*, gfp_t);
long (*nr_cached_objects)(struct super_block *,
struct shrink_control *);
long (*free_cached_objects)(struct super_block *,
struct shrink_control *);
};

1.2. 索引节点 inode

阅读全文 »

fde加密与解锁

加锁状态

有以下 几种加锁状态:

  • 默认
  • PIN 码
  • 密码
  • 解锁图案
  • 指纹
  • 人脸解锁

其中指纹加锁的前提是必须有pin/密码/解锁图案的其中一种。所以可以只看pin/密码/pattern图案解锁即可

首次启动默认密码的情况

首次启动时,设备会创建一个随机生成的 128 位主密钥,然后会使用默认密码存储的盐对其进行哈希处理。默认密码是default_password。不过,设备还会通过 TEE(例如 TrustZone)为生成的哈希签名。TEE 会使用相应签名的哈希来加密主密钥。

密码加密

在已加密的设备上设置安全密码或更改安全密码

当用户在设备上设置 PIN 码/pattern图案密码或password时,只有 128 位的密钥会被重新加密并存储起来,加密设备的主密钥并不会改变。
当用户选择在设置中更改或移除密码时,界面会向 vold 发送 cryptfs changepw 命令,然后 vold 会使用新密码重新加密磁盘主密钥。

阅读全文 »