一. 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的流程"进行说明。

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的流程"进行说明。

 

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设备中将一个文件拷贝到其他目录的流程分析完毕!

 

 

 

 

 

 

 

 

 

更多相关文章

  1. Android(安卓)SDK无法更新
  2. Android(安卓)ImageLoader 本地缓存
  3. Android的frameworks层音量控制原理分析
  4. 怎样在Android中解析doc、docx、xls、xlsx格式文
  5. android反编译工具 ApkDec-Release-0.1
  6. 【Android】Android(安卓)Input
  7. android error: Apostrophe not preceded 错误解决办法
  8. Android(安卓): java.lang.UnsatisfiedLinkError: dalvik.system
  9. android include 控件详解

随机推荐

  1. Android Shape Drawable 静态使用和动态
  2. Android自学笔记1——基础知识
  3. Android系统资源
  4. Android 3 开发环境搭建
  5. Android 的系统属性(SystemProperties)分
  6. Android的BroadcastReceiver简介
  7. android中的计步问题及计步传感器分析
  8. Android开发的未来发展方向,难道android真
  9. Android日记之2012\01\13
  10. Android绘图之LinearGradient线性渐变(9)