本文主要是介绍Android之 MTP框架和流程分析 (2),希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!
原文来自:http://www.cnblogs.com/skywang12345/p/3474206.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设备中将一个文件拷贝到其他目录的流程分析完毕!
这篇关于Android之 MTP框架和流程分析 (2)的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!