Android之MTP框架和流程分析(二)

一. MTP驅動註冊

MTP驅動文件是drivers/usb/gadget/f_mtp.c。它通過下面的代碼會映射到文件節點"/dev/mtp_usb"中。

1 static const char mtp_shortname[] = "mtp_usb";
2
3 static const struct file_operations mtp_fops = {
4 .owner = THIS_MODULE,
5 .read = mtp_read,
6 .write = mtp_write,
7 .unlocked_ioctl = mtp_ioctl,
8 .open = mtp_open,
9 .release = mtp_release,
10 };
11
12 static struct miscdevice mtp_device = {
13 .minor = MISC_DYNAMIC_MINOR,
14 .name = mtp_shortname,
15 .fops = &mtp_fops,
16 };
17
18 static int mtp_setup(void)
19 {
20 ...
21
22 ret = misc_register(&mtp_device);
23
24 ...
25 }

說明

(01) misc_register(&mtp_device)會將MTP註冊到虛擬文件系統"/dev"中,而對應的註冊的文件節點的名稱是"mtp_usb",即完成的節點是"/dev/mtp_usb"。

(02) 用戶空間操作節點"/dev/mtp_usb"就是調用MTP驅動中file_operations中的相關函數。

例如,用戶空間通過read()去讀取"/dev/mtp_usb",實際上調用內核空間的是mtp_read();用戶空間通過write()去寫"/dev/mtp_usb",實際上調用內核空間的是mtp_write()。

二. mtp_read()

該函數在f_mtp.c中實現,源碼如下:

 

1 static ssize_t mtp_read(struct file *fp, char __user *buf,
2 size_t count, loff_t *pos)
3 {
4// 從“USB”消息隊列中中讀取PC發給Android設備的請求,請求消息保存在req中。
5 ret = usb_ep_queue(dev->ep_out, req, GFP_KERNEL);
6 ...
7
8// 等待cpu將工作隊列(read_wq)上已有的消息處理完畢
9 ret = wait_event_interruptible(dev->read_wq, dev->rx_done);
10 ...
11
12// 將“PC的請求數據(req->)”從“內核空間”拷貝到“用戶空間”
13if (copy_to_user(buf, req->buf, xfer))
14 r = -EFAULT;
15
16 ...
17 }

 

說明mtp_read()會通過USB讀取"PC發給Android設備的請求",獲取到請求後,會通過copy_to_user()會將數據從"內核空間"拷貝到"用戶空間",這樣用戶就能在用戶空間讀取到該數據。

三. mtp_write()

1 static ssize_t mtp_write(struct file *fp, const char __user *buf,
2 size_t count, loff_t *pos)
3 {
4
5while (count > 0 || sendZLP) {
6
7// 將“用戶空間”傳來的消息(buf)拷貝到“內核空間”的req->buf中。
8if (xfer && copy_from_user(req->buf, buf, xfer))
9 ...
10
11// 將打包好的消息req放到USB消息隊列中。
12 ret = usb_ep_queue(dev->ep_in, req, GFP_KERNEL);
13 ...
14 }
15
16 ...
17 }

說明:mtp_write()會將"用戶空間"發來的消息拷貝到"內核空間",並將該消息打包;然後,將打包好的消息添加到USB消息隊列中。USB驅動負責將消息隊列中的消息傳遞給PC。

10 mServer.start()

MtpServer實際上是一個Runnable線程接口。在"MtpReceiver的handleUsbState()"中,初始化MtpServer之後,會啓動MtpServer。

MtpServer中的run()方法如下:

@Override
public void run() {
native_run();
native_cleanup();
}

從中,我們發現run()實際上是調用的本地方法native_run()。

10.1 native_run()

根據前面的gMethods表格,我們知道native_run()對應JNI層的android_mtp_MtpServer_run()方法,它的源碼如下:

 

1 static void android_mtp_MtpServer_run(JNIEnv *env, jobject thiz)
2 {
3// 返回MtpServer對象
4 MtpServer* server = getMtpServer(env, thiz);
5// 如果server不爲NULL,則調用它的run()方法。
6if (server)
7 server->run();
8else
9 ALOGE("server is null in run");
10 }

 

說明

(01) getMtpServer()返回的是MtpServer對象。這個前面已經介紹過!

(02) 調用MtpServer對象的run()方法。

10.2 run()

1 void MtpServer::run() {
2// 將mFD賦值給fd,fd就是“/dev/mtp_usb”的句柄
3int fd = mFD;
4
5while (1) {
6// 讀取“/dev/mtp_usb”
7int ret = mRequest.read(fd);
8
9 ...
10// 獲取“MTP操作碼”
11 MtpOperationCode operation = mRequest.getOperationCode();
12 MtpTransactionID transaction = mRequest.getTransactionID();
13
14 ...
15
16// 在handleRequest()中,根據讀取的指令作出相應的處理
17if (handleRequest()) {
18 ...
19 }
20 }
21
22 ...
23 }

說明

run()會不斷的從"/dev/mtp_usb"中讀取數據。如果是有效的指令,則在handleRequest()作出相應的處理。

(01) mRequest是MtpRequestPacket對象,MtpRequestPacket是解析"PC請求指令"的類。

(02) handleRequest()是具體處理"MTP各個指令的類"。例如,PC獲取Android設備的設備信息指令,是在handleRequest()中處理的。

10.3 while(1){...}

在run()中,會通過while(1)循環不斷的"執行read(),從"/dev/mtp_usb中讀取數據";然後調用handleRequest()對數據進行處理"。

read()函數最終會調用到Kernel的mtp_read(),mtp_read()已經在前面介紹過了。

handleRequest()則是對讀取出來的消息進行處理,它的源碼如下:

 

1 bool MtpServer::handleRequest() {
2 Mutex::Autolock autoLock(mMutex);
3
4 MtpOperationCode operation = mRequest.getOperationCode();
5 MtpResponseCode response;
6
7 mResponse.reset();
8
9if (mSendObjectHandle != kInvalidObjectHandle && operation != MTP_OPERATION_SEND_OBJECT) {
10 mSendObjectHandle = kInvalidObjectHandle;
11 }
12
13switch (operation) {
14case MTP_OPERATION_GET_DEVICE_INFO:
15 response = doGetDeviceInfo();
16break;
17case MTP_OPERATION_OPEN_SESSION:
18 response = doOpenSession();
19break;
20case MTP_OPERATION_CLOSE_SESSION:
21 response = doCloseSession();
22break;
23 ...
24case MTP_OPERATION_GET_OBJECT:
25 response = doGetObject();
26break;
27case MTP_OPERATION_SEND_OBJECT:
28 response = doSendObject();
29break;
30 ...
31 }
32
33if (response == MTP_RESPONSE_TRANSACTION_CANCELLED)
34return false;
35 mResponse.setResponseCode(response);
36return true;
37 }

 

總結:在"PC和Android設備"連接後,MtpReceiver會收到廣播。接着MtpReceiver會啓動MtpService,MtpService會啓動MtpServer(Java層)。MtpServer(Java)層會調用底層的JNI函數。在JNI中,會打開MTP文件節點"/dev/mtp_usb",然後MtpServer(JNI層)會不斷的從中讀取消息並進行處理。

第4部分 MTP協議之I->R流程

下面以"PC中打開一個MTP上的文件(讀取文件內容)"來對"MTP協議中Initiator到Reponser的流程"進行說明。PC讀取文件內容的時序圖如圖4-01所示:

圖4-01

1 read()

根據MTP啓動流程中分析可知: MTP啓動後,MtpServer.cpp中的MtpServer::run()會通過read()不斷地從"/dev/mtp_usb"中讀取出"PC發來的消息"。

2 handleRequest()

read()在讀取到PC來的消息之後,會交給MtpServer::handleRequest()進行處理。"PC讀取文件內容"的消息的ID是MTP_OPERATION_GET_OBJECT;因此,它會通過doGetObject()進行處理。

3. doGetObject()

MtpServer.cpp中doGetObject()的源碼如下:

1 MtpResponseCode MtpServer::doGetObject() {
2 ...
3
4// 根據handle獲取文件的路徑(pathBuf)、大小(fileLength)和“格式”。
5int result = mDatabase->getObjectFilePath(handle, pathBuf, fileLength, format);
6if (result != MTP_RESPONSE_OK)
7return result;
8
9// 將文件路徑轉換爲const char*類型。
10const char* filePath = (const char *)pathBuf;
11// 將文件的信息傳遞給mfr,mfr是kernel的一個結構體;最終將mfr傳遞給kernel。
12 mtp_file_range mfr;
13 mfr.fd = open(filePath, O_RDONLY); // 設置“文件句柄”。
14if (mfr.fd < 0) {
15return MTP_RESPONSE_GENERAL_ERROR;
16 }
17 mfr.offset = 0; // 設置“文件偏移”
18 mfr.length = fileLength;
19 mfr.command = mRequest.getOperationCode(); // 設置“command”
20 mfr.transaction_id = mRequest.getTransactionID(); // 設置“transaction ID”
21
22// 通過ioctl將文件傳給kernel。
23int ret = ioctl(mFD, MTP_SEND_FILE_WITH_HEADER, (unsigned long)&mfr);
24// 關閉文件
25 close(mfr.fd);
26
27 ...
28 }

說明

doGetDeviceInfo的流程就是,先通過JNI回調Java中的函數,(根據文件句柄)從MediaProvider數據庫中獲取文件的路徑、大小和格式等信息。接着,將這些信息封裝到kernel的"mtp_file_range結構體"中。最後,通過ioctl將"mtp_file_range結構體"傳遞給kernel。這樣,kernel就收到文件的相關信息了,kernel負責將文件信息通過USB協議傳遞給PC。

4 getObjectFilePath()

doGetObject()會調用的getObjectFilePath()。該函數在android_mtp_MtpDatabase.cpp中實現。

 

1 MtpResponseCode MyMtpDatabase::getObjectFilePath(MtpObjectHandle handle,
2 MtpString& outFilePath,
3 int64_t& outFileLength,
4 MtpObjectFormat& outFormat) {
5 ...
6
7// 調用MtpDatabase.java中的getObjectFilePath()。
8// 作用是根據文件句柄,獲取“文件路徑”、“文件大小”、“文件格式”
9 jint result = env->CallIntMethod(mDatabase, method_getObjectFilePath,
10 (jint)handle, mStringBuffer, mLongBuffer);
11
12// 設置“文件路徑”
13 jchar* str = env->GetCharArrayElements(mStringBuffer, 0);
14 outFilePath.setTo(str, strlen16(str));
15 env->ReleaseCharArrayElements(mStringBuffer, str, 0);
16
17// 設置“文件大小”和“文件格式”
18 jlong* longValues = env->GetLongArrayElements(mLongBuffer, 0);
19 outFileLength = longValues[0];
20 outFormat = longValues[1];
21 env->ReleaseLongArrayElements(mLongBuffer, longValues, 0);
22
23
24 ...
25 }

 

說明

MyMtpDatabase::getObjectFilePath()實際上通過MtpDatabase.java中的getObjectFilePath()來獲取文件的相關信息的。

5 getObjectFilePath()

MtpDatabase.java中getObjectFilePath()的源碼如下:

 

1 private int getObjectFilePath(int handle, char[] outFilePath, long[] outFileLengthFormat) {
2 ...
3
4// 在MediaProvider中查找文件對應的路徑。 5// mObjectsUri是根據文件handle獲取到的URI。
6 c = mMediaProvider.query(mPackageName, mObjectsUri, PATH_FORMAT_PROJECTION,
7 ID_WHERE, new String[] { Integer.toString(handle) }, null, null);
8if (c != null && c.moveToNext()) {
9// 獲取“文件路徑”
10 String path = c.getString(1);
11 path.getChars(0, path.length(), outFilePath, 0);
12 outFilePath[path.length()] = 0;
13// 獲取“文件大小”
14 outFileLengthFormat[0] = new File(path).length();
15// 獲取“文件格式”
16 outFileLengthFormat[1] = c.getLong(2);
17return MtpConstants.RESPONSE_OK;
18 } else {
19return MtpConstants.RESPONSE_INVALID_OBJECT_HANDLE;
20 }
21
22 ...
23 }

 

說明:getObjectFilePath()會從MediaProvider的數據庫中查找相應的文件,從而獲取文件信息。然後,將文件的信息回傳給JNI。

6 ioctl

MtpServer.cpp中doGetObject()中獲取到文件信息之後,會通過ioctl將MTP_SEND_FILE_WITH_HEADER消息傳遞給Kernel。

根據前面介紹的Kernel內容克制,ioctl在file_operations中註冊,ioctl實際上會執行mtp_ioctl()函數。它的源碼如下:

 

1 static long mtp_ioctl(struct file *fp, unsigned code, unsigned long value)
2 {
3 ...
4
5switch (code) {
6case MTP_SEND_FILE:
7case MTP_RECEIVE_FILE:
8case MTP_SEND_FILE_WITH_HEADER:
9 {
10struct mtp_file_range mfr;
11struct work_struct *work;
12
13// 將“用戶空間”傳來的值(value)拷貝到“內核空間”的mfr中。
14if (copy_from_user(&mfr, (void __user *)value, sizeof(mfr))) {
15 ret = -EFAULT;
16goto fail;
17 }
18// 獲取文件句柄
19 filp = fget(mfr.fd);
20if (!filp) {
21 ret = -EBADF;
22goto fail;
23 }
24
25// 將“文件相關的信息”賦值給dev。
26 dev->xfer_file = filp;
27 dev->xfer_file_offset = mfr.offset;
28 dev->xfer_file_length = mfr.length;
29 smp_wmb();
30
31if (code == MTP_SEND_FILE_WITH_HEADER) {
32// 設置work的值
33 work = &dev->send_file_work;
34 dev->xfer_send_header = 1;
35 dev->xfer_command = mfr.command;
36 dev->xfer_transaction_id = mfr.transaction_id;
37 }
38 ...
39
40// 將work添加到工作隊列(dev->wq)中。
41 queue_work(dev->wq, work);
42// 對工作隊列執行flush操作。
43 flush_workqueue(dev->wq);
44
45break;
46 }
47
48 ...
49 }

 

說明:mtp_ioctl()在收到MTP_SEND_FILE_WITH_HEADER消息之後,會獲取文件相關信息。然後將"work"添加到工作隊列dev->wq中進行調度。work對應的函數指針是send_file_work()函數;這就意味着,工作隊列會制定send_file_work()函數。

send_file_work()的源碼如下:

 

1 static void send_file_work(struct work_struct *data)
2 {
3// 獲取dev結構體
4struct mtp_dev *dev = container_of(data, struct mtp_dev,
5 send_file_work);
6 ...
7
8// 讀取文件消息
9 smp_rmb();
10 filp = dev->xfer_file;
11 offset = dev->xfer_file_offset;
12 count = dev->xfer_file_length;
13
14
15while (count > 0 || sendZLP) {
16
17 ...
18
19// 從文件中讀取數據到內存中。
20 ret = vfs_read(filp, req->buf + hdr_size, xfer - hdr_size,
21 &offset);
22
23 ...
24
25// 將req添加到usb終端的隊列中
26 ret = usb_ep_queue(dev->ep_in, req, GFP_KERNEL);
27
28 ...
29 }
30
31 ...
32 }

 

說明:send_file_work()的作用就是不斷地將文件中的數據讀取到內存中,並封裝到USB請求結構體req中。然後,再將數據req傳遞到USB工作隊列,USB賦值將文件內容傳遞給PC。

至此,PC讀取文件內容的流程分析完畢!

第5部分 MTP協議之R->I流程

下面以"Android設備中將一個文件拷貝到其他目錄"來對"MTP協議中Reponser到Initiator的流程"進行說明。對應的時序圖如圖5-01所示:

圖5-01

1 MediaProvider.java中sendObjectAdded()

在Android設備上將一個文件拷貝到其他目錄。文件瀏覽器中會發出一個Intent事件,通知MediaProvider更新數據庫。MediaProvider更新了數據庫之後,會通知MTP進行同步處理。MediaProvider通知MTP的源碼如下:

 

1 private void sendObjectAdded(long objectHandle) {
2synchronized (mMtpServiceConnection) {
3if (mMtpService != null) {
4try {
5 mMtpService.sendObjectAdded((int)objectHandle);
6 } catch (RemoteException e) {
7 Log.e(TAG, "RemoteException in sendObjectAdded", e);
8 mMtpService = null;
9 }
10 }
11 }
12 }

 

說明:該函數會通知MtpService,Android設備中新建了個文件。

2 MtpService.java中sendObjectAdded()

MtpService.java中sendObjectAdded()的源碼如下:

 

1 MtpService.java中sendObjectAdded()的源碼如下:
2 public void sendObjectAdded(int objectHandle) {
3synchronized (mBinder) {
4if (mServer != null) {
5 mServer.sendObjectAdded(objectHandle);
6 }
7 }
8 }

 

說明:該函數會發送消息給mServer,而mServer是MtpServer對象。

3 MtpServer.java中的sendObjectAdded

1 public void sendObjectAdded(int handle) {
2 native_send_object_added(handle);
3 }

 

說明:該函數會調研JNI本地方法。

4 native_send_object_added()

native_send_object_added()在android_mtp_MtpServer.cpp中實現。根據前面介紹相關內容可知,native_send_object_added()和android_mtp_MtpServer_send_object_added()對應。後者的源碼如下:

 

1 static void android_mtp_MtpServer_send_object_added(JNIEnv *env, jobject thiz, jint handle)
2 {
3 Mutex::Autolock autoLock(sMutex);
4
5// 獲取MtpServer,然後調用MtpServer的sendObjectAdded()方法。
6 MtpServer* server = getMtpServer(env, thiz);
7if (server)
8 server->sendObjectAdded(handle);
9else
10 ALOGE("server is null in send_object_added");
11 }

 

說明:該函數會將消息發送給MtpServer。

5 MtpServer.cpp中的sendObjectAdded

MtpServer.cpp中sendObjectAdded()的源碼如下:

 

1 void MtpServer::sendObjectAdded(MtpObjectHandle handle) {
2 sendEvent(MTP_EVENT_OBJECT_ADDED, handle);
3 }

 

說明:sendEvent()的作用,我們前面已經介紹過了。它在此處的目的,就是通過ioctl將消息發送給Kernel。

6 Kernel中的ioctl

在Kernel中,ioctl消息會調用mtp_ioctl()進行處理。它的源碼如下:

 

1 static long mtp_ioctl(struct file *fp, unsigned code, unsigned long value)
2 {
3 ...
4
5case MTP_SEND_EVENT:
6 {
7struct mtp_event event;
8// 將“用戶空間”傳來的值(value)拷貝到“內核空間”的event中。
9if (copy_from_user(&event, (void __user *)value, sizeof(event)))
10 ret = -EFAULT;
11else
12 ret = mtp_send_event(dev, &event);
13
14 ...
15 }
16
17 ...
18 }

 

說明:對於MTP_SEND_EVENT消息,mtp_ioctl會先將"用戶空間"傳遞來的消息拷貝到"內核空間";然後,調用mtp_send_event()進行處理。

7 Kernel中的mtp_send_event()

1 static int mtp_send_event(struct mtp_dev *dev, struct mtp_event *event)
2 {
3 ...
4
5// 將“用戶空間”傳來的“消息的內容(event->data)”拷貝到“內核空間”中
6if (copy_from_user(req->buf, (void __user *)event->data, length))
7 ...
8
9// 將req請求添加到USB隊列中。
10 ret = usb_ep_queue(dev->ep_intr, req, GFP_KERNEL);
11
12 ...
13 }

 

說明:mtp_send_event()會先將"用戶空間"傳遞來的消息的具體內容拷貝到"內核空間",並將該消息封裝在一個USB請求對象req中;然後,將USB請求添加到USB隊列中。USB驅動負責將該數據通過USB線發送給PC。

至此,Android設備中將一個文件拷貝到其他目錄的流程分析完畢!


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章