您的位置:首页 > 移动开发 > Android开发

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

2014-11-09 16:32 288 查看
原文来自:/article/4708130.html第三四五部分


一. 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->)”从“内核空间”拷贝到“用户空间”
13     if (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
5     while (count > 0 || sendZLP) {
6
7         // 将“用户空间”传来的消息(buf)拷贝到“内核空间”的req->buf中。
8         if (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()方法。
6     if (server)
7         server->run();
8     else
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”的句柄
3     int fd = mFD;
4
5     while (1) {
6         // 读取“/dev/mtp_usb”
7         int ret = mRequest.read(fd);
8
9         ...
10         // 获取“MTP操作码”
11         MtpOperationCode operation = mRequest.getOperationCode();
12         MtpTransactionID transaction = mRequest.getTransactionID();
13
14         ...
15
16         // 在handleRequest()中,根据读取的指令作出相应的处理
17         if (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
9     if (mSendObjectHandle != kInvalidObjectHandle && operation != MTP_OPERATION_SEND_OBJECT) {
10         mSendObjectHandle = kInvalidObjectHandle;
11     }
12
13     switch (operation) {
14         case MTP_OPERATION_GET_DEVICE_INFO:
15             response = doGetDeviceInfo();
16             break;
17         case MTP_OPERATION_OPEN_SESSION:
18             response = doOpenSession();
19             break;
20         case MTP_OPERATION_CLOSE_SESSION:
21             response = doCloseSession();
22             break;
23         ...
24         case MTP_OPERATION_GET_OBJECT:
25             response = doGetObject();
26             break;
27         case MTP_OPERATION_SEND_OBJECT:
28             response = doSendObject();
29             break;
30         ...
31     }
32
33     if (response == MTP_RESPONSE_TRANSACTION_CANCELLED)
34         return false;
35     mResponse.setResponseCode(response);
36     return 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)和“格式”。
5     int result = mDatabase->getObjectFilePath(handle, pathBuf, fileLength, format);
6     if (result != MTP_RESPONSE_OK)
7         return result;
8
9     // 将文件路径转换为const char*类型。
10     const char* filePath = (const char *)pathBuf;
11     // 将文件的信息传递给mfr,mfr是kernel的一个结构体;最终将mfr传递给kernel。
12     mtp_file_range  mfr;
13     mfr.fd = open(filePath, O_RDONLY);                 // 设置“文件句柄”。
14     if (mfr.fd < 0) {
15         return 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。
23     int 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);
8     if (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);
17         return MtpConstants.RESPONSE_OK;
18     } else {
19         return 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
5     switch (code) {
6     case MTP_SEND_FILE:
7     case MTP_RECEIVE_FILE:
8     case MTP_SEND_FILE_WITH_HEADER:
9     {
10         struct mtp_file_range    mfr;
11         struct work_struct *work;
12
13         // 将“用户空间”传来的值(value)拷贝到“内核空间”的mfr中。
14         if (copy_from_user(&mfr, (void __user *)value, sizeof(mfr))) {
15             ret = -EFAULT;
16             goto fail;
17         }
18         // 获取文件句柄
19         filp = fget(mfr.fd);
20         if (!filp) {
21             ret = -EBADF;
22             goto 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
31         if (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
45         break;
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结构体
4     struct 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
15     while (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) {
2     synchronized (mMtpServiceConnection) {
3         if (mMtpService != null) {
4             try {
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) {
3     synchronized (mBinder) {
4         if (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);
7     if (server)
8         server->sendObjectAdded(handle);
9     else
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
5     case MTP_SEND_EVENT:
6     {
7         struct mtp_event    event;
8         // 将“用户空间”传来的值(value)拷贝到“内核空间”的event中。
9         if (copy_from_user(&event, (void __user *)value, sizeof(event)))
10             ret = -EFAULT;
11         else
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)”拷贝到“内核空间”中
6     if (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设备中将一个文件拷贝到其他目录的流程分析完毕!
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: