美文网首页
[038]Binder传输fd细节

[038]Binder传输fd细节

作者: 王小二的技术栈 | 来源:发表于2020-03-13 19:15 被阅读0次

    前言

    最近在研究Linux IO相关的知识,突然想起来Binder机制可以传递fd,但是没有仔细考虑过下面这个问题。

    Client端fd和Server端fd,内核中指向两个的file结构体还是指向同一个file结构体?

    一、两者的区别

    1.1 有人可能会问:两者有什么区别?

    区别大了,如果指向同一个file结构体,就意味的两个进程共享同一个f_pos读写指针。

    1.2 f_pos读写指针是什么?

    f_pos读写指针是用于记录当前文件读写的位置

    举个例子:
    假设一个文件1.txt的内容是"helloworld"。
    进程A和进程B对应的fd指向同一个file,就会共享f_pos。
    进程A先读5个字节,就会读到"hello"
    进程B再读5个字节,就会读到"world"

    二、Binder驱动源码

    以下代码运行在Client端的线程,并且都在内核中

    2.1 binder_translate_fd

    static int binder_translate_fd(int fd,
                       struct binder_transaction *t,
                       struct binder_thread *thread,
                       struct binder_transaction *in_reply_to)
    {
        //获取Server端的binder_proc
        struct binder_proc *target_proc = t->to_proc;
        int target_fd;
        struct file *file;
        //获得Client端中fd对应的file结构体
        file = fget(fd);
        //获取Server端的一个空闲的target_fd
        target_fd = task_get_unused_fd_flags(target_proc, O_CLOEXEC);
        //将target_fd和file绑定
        task_fd_install(target_proc, target_fd, file);//跳转2.2
        //返回server端的fd,也就是target_fd
        return target_fd;
    }
    

    2.2 task_fd_install

    static void task_fd_install(
        struct binder_proc *proc, unsigned int fd, struct file *file)
    {
        mutex_lock(&proc->files_lock);
        if (proc->files)
            __fd_install(proc->files, fd, file);//跳转2.3
        mutex_unlock(&proc->files_lock);
    }
    

    2.3 __fd_install

    void __fd_install(struct files_struct *files, unsigned int fd,
                  struct file *file)
    {
        //每一个进程关联着一个files_struct结构体
        //files_struct结构体中有一个fdtable结构体
        struct fdtable *fdt;
        //获取files_struct中的fdtable
        fdt = rcu_dereference_sched(files->fdt);
        //fdtable保存了一个file指针数组fd
        //将fd[fd]指向file结构体,这两个fd不同,前者表示指针数组,后者表示形参中int fd
        rcu_assign_pointer(fdt->fd[fd], file);
    }
    

    整个关系如下图:


    2.4 小结

    从源码来看binder传输fd,指向同一个file结构体,共享同一个f_pos读写指针


    三、写的Demo

    3.1 Client端

    public class MainActivity extends AppCompatActivity implements View.OnClickListener {
    
        private IBinder mSendFd;
        private TextView mTxtSend;
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);
            mTxtSend = findViewById(R.id.txt_send);
            mTxtSend.setOnClickListener(this);
            ping();
            writeFile();
    
        }
    
        //绑定Binder
        private void ping() {
            Intent intent = new Intent(this, RemoteService.class);
            bindService(intent, new ServiceConnection() {
                @Override
                public void onServiceConnected(ComponentName name, IBinder service) {
                    mSendFd = service;
                }
    
                @Override
                public void onServiceDisconnected(ComponentName name) {
    
                }
            }, Context.BIND_AUTO_CREATE);
        }
    
        //新建一个文件
        private void writeFile() {
            try {
                String content = "helloworld";
                File file = new File(this.getFilesDir().getPath() + "/" + "1.txt");
                if (!file.exists()) {
                    file.createNewFile();
                }
                FileWriter fileWriter = new FileWriter(file.getAbsoluteFile());
                BufferedWriter bw = new BufferedWriter(fileWriter);
                bw.write(content);
                bw.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    
        @Override
        public void onClick(View v) {
            try {
                FileDescriptor fd = readFile();
                Parcel data = Parcel.obtain();
                data.writeFileDescriptor(fd);
                mSendFd.transact(1, data, null, 0);
            } catch (Exception e) {
    
            }
        }
    
        //读取5个字符
        private FileDescriptor readFile() {
            try {
                FileDescriptor fd = Os.open(this.getFilesDir().getPath() + "/" + "1.txt", OsConstants.O_RDONLY, 0600);
                byte[] buf = new byte[5];
                Os.read(fd, buf, 0, buf.length);
                Log.v("KobeWang3", "This is Client : " + new String(buf));
                return fd;
            } catch (Exception e) {
                e.printStackTrace();
            }
            return null;
        }
    }
    

    3.2 Server端

    public class RemoteService extends Service {
        @Nullable
        @Override
        public IBinder onBind(Intent intent) {
            return new SendBinder();
        }
    
        public class SendBinder extends Binder {
            @Override
            protected boolean onTransact(int code, @NonNull Parcel data, @Nullable Parcel reply, int flags) throws RemoteException {
                if (code == 1) {
                    try {
                        FileDescriptor fd = data.readFileDescriptor().getFileDescriptor();
                        FileReader fileReader = new FileReader(fd);
                        BufferedReader bf = new BufferedReader(fileReader);
                        String str = bf.readLine();
                        Log.v("KobeWang3", "This is Server : " + str);
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                    return true;
                }
                return super.onTransact(code, data, reply, flags);
            }
        }
    }
    

    别忘了让RemoteService运行在其他进程

    <service
        android:name=".RemoteService"
        android:exported="true"
        android:process=":remote">
    </service>
    

    3.3 运行结果

    果然结果和我们分析的一致

    11747 11747 V KobeWang3: This is Client : hello
    11810 11830 V KobeWang3: This is Server : world
    

    四、ParcelFileDescriptor

    ParcelFileDescriptor是android提供的,继承于Parcelable,可以在AIDL中直接使用。

    4.1 用法

    用java层File对象创建ParcelFileDescriptor

    public static ParcelFileDescriptor open(File file, int mode) throws FileNotFoundException {
            final FileDescriptor fd = openInternal(file, mode);
            if (fd == null) return null;
    
            return new ParcelFileDescriptor(fd);
        }
    
    private static FileDescriptor openInternal(File file, int mode) throws FileNotFoundException {
            final int flags = FileUtils.translateModePfdToPosix(mode) | ifAtLeastQ(O_CLOEXEC);
    
            int realMode = S_IRWXU | S_IRWXG;
            if ((mode & MODE_WORLD_READABLE) != 0) realMode |= S_IROTH;
            if ((mode & MODE_WORLD_WRITEABLE) != 0) realMode |= S_IWOTH;
    
            final String path = file.getPath();
            try {
                return Os.open(path, flags, realMode);//重新open一次。
            } catch (ErrnoException e) {
                throw new FileNotFoundException(e.getMessage());
            }
        }
    

    4.2 注意点

    file1:java层File对象对应的fd1指向内核空间的file结构体
    file2:ParcelFileDescriptor会根据path重新open,新建一个fd2指向内核空间新建的file结构体
    虽然file1和file2指向同一个实体文件,但是两者的读写指针是独立的。
    经过Binder通信传递ParcelFileDescriptor对象。Server端拿到的fd1指向的是file2。
    假如Client端用java层File对象读文件,Server端拿到的ParcelFileDescriptor对应的fd1读写文件,两者并不会有任何影响。

    五、为什么要学Linux Kernel

    作为Java程序员出身我,其实对Linux Kernel并不熟悉,一年前,我开始努力尝试学习Linux Kernel,发现自己对很多上层的细节,有了更加深入的理解,我相信我继续努力学下去。

    相关文章

      网友评论

          本文标题:[038]Binder传输fd细节

          本文链接:https://www.haomeiwen.com/subject/zytlshtx.html