Android Fk: PKMS(3)之installd及LocalSocket实现Java层与Native层通信

来源:互联网 发布:php 点击链接下载文件 编辑:程序博客网 时间:2024/06/05 21:02

LOCAL_CLANG := true#Android Fk: PKMS(3)之installd及LocalSocket实现Java层与Native层通信

一、installd的概述

  从上一篇介绍应用安装与卸载的学习文档中知道PKMS在实现部分包管理功能时需要借助installd去完成,关于调用的详细流程可以参考这篇博客,Android7.0 PackageManagerService (5) installd,作者详细介绍了installd的初始化及调用方法的流程,查看android 7.1.1的源码,这部分代码和博主所述大致一致,本人就不赘述了。

1.installd的启动

  installd是个native的服务,在system/bin下,开机时由init启动:
这里写图片描述

  看installd的rc文件:

service installd /system/bin/installd    class main    socket installd stream 600 system system

  可以看到在起installd的时候创建了一个名为installd的socket文件,查看如下:
这里写图片描述
  看到dev/socket下还有其他服务创建的socket文件;

  由上面提到的博客分析得知 installd启动后,获取作为服务端的socket “installd”; 然后,监听”installd”,等待Java层installer服务的连接及命令的到来:

2.installd的调用方试

  作为客户端的PKMS使用Intaller中封装好的用于socket通信的InstallerConnection对应向对应的socket”installd”发生操作指令;
  PKMS调到installd的大致流程总结如图(详细流程参考上面提到的博客):
  这里写图片描述
  PKMS调用installd的方式是通过localsocket的方式实现的,socket实现了从Java层到Native层的通信,下面将通过一个demo学习来使用下这种socket通信方式;

二、Socket方式实现Java层与Native层通信

1.模仿installd写一个开机启动的native服务

1.1 native服务源码

  在framework/base/cmd下新建一个demo文件夹命名为socket_test,或者在installd的模块目录framework/native/cmd下建项目目录也可以,然后新建c++文件,socket_test.cpp,如下:

#include <stdio.h>#include <stdlib.h>#include <errno.h>#include <string.h>#include <sys/types.h>#include <netinet/in.h>#include <sys/socket.h>#include <sys/wait.h>#include <sys/un.h>#include <cutils/sockets.h>#include <utils/Log.h>#include <android/log.h>#define SOCKET_NAME "socket_test"#define  LOG_TAG    "SOCKET_TEST_SERVER"#define  LOGD(...)  __android_log_write(ANDROID_LOG_DEBUG, LOG_TAG, __VA_ARGS__)int main(){    char log[200];    LOGD("main");    int connect_number = 6;    int fdListen = -1, new_fd = -1;    int ret;    struct sockaddr_un peeraddr;    socklen_t socklen = sizeof (peeraddr);    int numbytes ;    char buff[256];    //获取SOCKET_NAME的socket文件描述符    fdListen = android_get_control_socket(SOCKET_NAME);    if (fdListen < 0) {        sprintf(log,"Failed to get socket '" SOCKET_NAME "' errno:%d , main listen will exit!", errno);        LOGD(log);        exit(-1);    }    //监听客户端连接,最多连接connect_number个    ret = listen(fdListen, connect_number);    sprintf(log,"Listen result %d",ret);    LOGD(log);    if (ret < 0) {        perror("listen");        exit(-1);    }    //获取客户端的连接    new_fd = accept(fdListen, (struct sockaddr *) &peeraddr, &socklen);    sprintf(log,"Accept_fd: %d",new_fd);    LOGD(log);    if (new_fd < 0 ) {        sprintf(log,"fd<0 error %d",errno);        LOGD(log);        exit(-1);    }    while(1) {        LOGD("Waiting for Client ...");        if((numbytes = recv(new_fd,buff,sizeof(buff),0))==-1) {            sprintf(log,"%d",errno);            LOGD(log);            continue;        } else {            sprintf(log,"Server Received: %s",buff);            LOGD(log);        }        //将收到的buff信息再send回给client端        if(send(new_fd,buff,strlen(buff),0)==-1) {            close(new_fd);            LOGD("send error!");            exit(0);        } else {            LOGD("Server sendback succuess!");        }    }    LOGD("main close ");    close(new_fd);    close(fdListen);    return 0;}

  主要流程和installd类似,大概的操作如下:
  启动后获取对应的socket文件描述符作为socket的server端,然后监听是否有client连接,client连接后接受client发送的消息,然后将消息再通过socket方式send回给client端;

1.2 Android.mk文件

  然后同目录下新建Android.mk文件,如下:

#frameworks/base/cmds/socket_test/Android.mkLOCAL_PATH:= $(call my-dir)common_src_files := socket_test.cppinclude $(CLEAR_VARS)LOCAL_SRC_FILES := $(common_src_files)LOCAL_CFLAGS += -DGL_GLEXT_PROTOTYPES -DEGL_EGLEXT_PROTOTYPESLOCAL_SHARED_LIBRARIES := \    libcutils \    liblog \    libandroidfw \    libutils \    libselinux LOCAL_MODULE := socket_testLOCAL_INIT_RC := socket_test.rcinclude $(BUILD_EXECUTABLE)

  其中值得注意的是LOCAL_INIT_RC这个标识,由于需要让这个demo服务通过init启动,因此需要给socket_test写自己的rc文件,有LOCAL_INIT_RC标识,在编该模块的时候会将该rc文件拷贝到system/etc/init目录下,init会去解析这个目录下的所以rc文件然后做对应的操作;

1.3 socket_test.rc文件

  接着定义socket_test的rc文件:

service socket_test /system/bin/socket_test    class main    socket socket_test stream 660 system system#保证开机结束后启动on property:sys.boot_completed=1    start socket_test

  在这里方便验证,直接将这个socket_init.rc文件push到system/etc/init/下面,但是需要在重启前将rc文件权限改为和其他rc文件一致,否则可能导致服务起不来,甚至无法开机,可以看到权限改为644:
这里写图片描述

adb rootadb remountadb push XX/socket_test.rc system/etc/init/adb shellchmod 644 system/etc/init/socket_test.rc

  到这一步我们可以尝试make socket_test -j8是否生成了对应的服务,这一步会在”out/target/product/pollux/system/bin/”生成socket_test服务,另外会将socket_test.rc更新到”out/target/product/pollux/system/etc/init/”目录下,全编的时候会将这个目录打包到手机对应的system/etc/init/目录下,然后开机去解析;
  将该服务push到system/bin/下,然后更改权限,
这里写图片描述

adb push out/target/product/pollux/system/bin/socket_test system/binadb chmod 755 socket_test

1.4 小问题解决

1.4.1 SELinux domain未定义导致socket_test未启动
  满怀激动的重启后,发现socket_test并未启动,而且dev/socket下也没有生成rc中定义好的socket文件,
查看开机log发现如下:

03-08 10:00:19.212502     0     0 E init    : Service socket_test does not have a SELinux domain defined.

  说明没有定义SELinux domain,导致服务无法自启动。需按如下方式修改或添加sepolicy文件:
   a. 在system/sepolicy/file_contexts文件末尾添加

############################## socket_test# System files/system/bin/socket_test u:object_r:socket_test_exec:s0

   b.在system/sepolicy/文件夹下新建socket_test.te文件,内容如下:

type socket_test, domain;type socket_test_exec, exec_type, file_type;init_daemon_domain(socket_test)

   c.编译bootimage,烧录bootimage,执行如下命令后再重启查看socket_test进程是否起来:

adb rootadb remountadb shell restorecon system/bin/xxxadb reboot

1.4.2 selinux问题导致socket文件未创建,然后创建了socket文件server又无法获取等问题
  但是看到dev/socket/下socket_test可能还是没有被创建,这里其实是selinux的问题了,因此需要根据log里显示被拒权限相应的添加这些权限;
  类似于如下的log:

03-08 10:31:04.219000  3738  3738 I auditd  : type=1400 audit(0.0:645): avc: denied { create } for comm="init" name="socket_test" scontext=u:r:init:s0 tcontext=u:object_r:socket_device:s0 tclass=sock_file permissive=103-08 10:31:08.719000  4181  4181 W socket_test: type=1400 audit(0.0:650): avc: denied { read write } for path="/dev/oeminfo" dev="tmpfs" ino=17725 scontext=u:r:socket_test:s0 tcontext=u:object_r:oeminfo_device:s0 tclass=chr_file permissive=103-08 11:24:27.919  3711  3711 I auditd  : type=1400 audit(0.0:666): avc: denied { setattr } for comm="init" name="socket_test" dev="tmpfs" ino=35596 scontext=u:r:init:s0 tcontext=u:object_r:socket_device:s0 tclass=sock_file permissive=0

  后面还会有其他奇怪的问题,比如socket文件创建好了,单socket_test服务无法获取dev/socket/下的socket_test文件作为服务端,log显示:

03-08 11:11:31.384     0     0 E init    : Failed to lchown socket '/dev/socket/socket_test': Permission denied看到该log前面一点出现03-08 11:24:27.919  3711  3711 W init    : type=1400 audit(0.0:666): avc: denied { setattr } for name="socket_test" dev="tmpfs" ino=35596 scontext=u:r:init:s0 tcontext=u:object_r:socket_device:s0 tclass=sock_file permissive=1

  所以这同样是SElinux问题,添加selinux权限的方法如下,在system/sepolicy/文件夹下添加的socket_test.te末尾添加权限:

#allow scontext tcontext:tclass { perm1 perm2 } 大致的添加方式allow init socket_device:sock_file{ create unlink link setattr};allow socket_test oeminfo_device:chr_file { read write };allow socket_test rootfs:lnk_file { getattr setattr };

  selinux也是修改了system/sepolicy中的文件,同样如上编bootimage,烧录,restorecon操作,重启,
现在终于看到dev/socket/下有socket_test文件了,system/bin/下的socket_test服务同样也起来了,而且也已成功获取socket_test文件作为服务端,此时正在监听client端连接。
这里写图片描述

这里写图片描述

  至此模仿installd,完成了native层的服务端,下面来完成java层的client短。

2. 写个Apk作为Client端向Native 服务 socket_test进行通信

2.1 apk核心代码

  为了操作方便直接写成apk作为client端,一个消息输入框,一个发送按钮,一个显示server发回的消息;
  生成系统签名的该apk,push到system/app下,给apk以系统的shareUid:

<manifest xmlns:android="http://schemas.android.com/apk/res/android"    package="com.demo.mysocketclient"    android:sharedUserId="android.uid.system">

  主要代码:

public class MainActivity extends AppCompatActivity {    Button btn_send;    EditText etxt;    TextView txtRev;    private final String SOCKET_NAME = "socket_test";    LocalSocket client;    LocalSocketAddress address;    private InputStream mIn;    private OutputStream mOut;    BufferedReader in;    @Override    protected void onCreate(Bundle savedInstanceState) {        super.onCreate(savedInstanceState);        setContentView(R.layout.activity_main);        client = new LocalSocket();        address = new LocalSocketAddress(SOCKET_NAME, LocalSocketAddress.Namespace.RESERVED);        btn_send = (Button) findViewById(R.id.btn_send);        etxt = (EditText) findViewById(R.id.etxt_msg);        txtRev = (TextView) findViewById(R.id.txt_receved);        try {            client.connect(address);        } catch (IOException e) {            e.printStackTrace();        }        try {            mOut = client.getOutputStream();            mIn = client.getInputStream();            in = new BufferedReader(new InputStreamReader(mIn));        } catch (IOException e) {            e.printStackTrace();        }        btn_send.setOnClickListener(new View.OnClickListener() {            @Override            public void onClick(View v) {                //这里需要在末尾加个换行符,否则在server发送返回回来的时候下面的in.readLine()会阻塞住                String msg = etxt.getText().toString() + "\n";                String rev = SendMsg(msg);                if (rev != null) {                    txtRev.setText(rev.toString());                } else {                    Log.d("DCYY", "rev is null!");                }            }        });    }    @Override    protected void onStop() {        super.onStop();        if (client != null && client.isConnected()) {            try {                client.close();                mOut.close();                mIn.close();                in.close();            } catch (IOException e) {                e.printStackTrace();            }        }    }    private String SendMsg(String s) {        final byte[] message = s.getBytes();        final int len = message.length;        if ((len < 1))            return "empty msg!";        try {            //向socket服务端发送消息            mOut.write(message);            SystemClock.sleep(1000);            return in.readLine();        } catch (IOException e) {            e.printStackTrace();        }        return "send failed!";    }}

2.2 小问题解决

  这里selinux权限还有冲突,比如app需要获取连接到dev/socket/下面的socket_test文件用于socket通信,但是却被Selinux权限拒绝了,每次要进行socket连接的时候总是报错,显示的log如下:

08-06 19:19:29.654 5964-5964/com.demo.mysocketclient W/.mysocketclient: type=1400 audit(0.0:1035): avc: denied { write } for name="socket_test" dev="tmpfs" ino=519 scontext=u:r:system_app:s0 tcontext=u:object_r:socket_device:s0 tclass=sock_file permissive=008-06 19:19:29.664 5964-5964/com.demo.mysocketclient W/System.err: java.io.IOException: Permission denied08-06 19:19:29.664 5964-5964/com.demo.mysocketclient W/System.err:     at android.net.LocalSocketImpl.connectLocal(Native Method)08-06 19:19:29.664 5964-5964/com.demo.mysocketclient W/System.err:     at android.net.LocalSocketImpl.connect(LocalSocketImpl.java:292)08-06 19:19:29.665 5964-5964/com.demo.mysocketclient W/System.err:     at android.net.LocalSocket.connect(LocalSocket.java:131)08-06 19:19:29.665 5964-5964/com.demo.mysocketclient W/System.err:     at com.demo.mysocketclient.MainActivity.onCreate(MainActivity.java:40)

  加上WRITE_EXTERNAL_STORAGE这个权限都不管用,这是selinux问题,对照上面添加上selinux权限:

allow system_app socket_device:sock_file{ read write };allow system_app socket_test:unix_stream_socket{ connectto };

  可是发现编bootimage又编不过了,原来app.te规定了appdomain是不允许有这个权限的,冲突导致编不过bootimage了。

system/sepolicy/app.te# Sockets under /dev/socket that are not specifically typed.neverallow appdomain socket_device:sock_file write;

  所以还是先偷个懒把selinux关了,日后再找方法解决这个问题吧:

adb rootadb remountadb shell setenforce 0

3. 从Java应用层通过socket方式与Native层service进行通信

  ok,下面所有selinux问题都不用管了,大胆尝试:
  在输入框中写上要发送的消息,然后点击发送,从log中看的出,此时socket_test是收到了client端发来的消息了:
这里写图片描述

  从Client端apk界面显示来看,也收到了由native服务socket_test发回的信息:
这里写图片描述

  至此,成功的完成从java应用层发送消息给native层的服务,并打印出消息,同时也实现了从native服务层发送消息到达java应用层;

三、总结

  1.看到PKMS是通过localsocket的方式与installd进行通信的,install在收到消息后根据消息的指令及数据进行接下来的功能实现,比较灵活。
  2.localsocket是对linux中的socket的封装,日后再看它其他重要的使用方式已经socket通信方式的重点;
  3.模仿installd写了一个本地服务,并通过localsocket方式实现了java应用层与native层的通信,其实细细想想,这样的方法可以用来做很多事情,实现自己需要的功能,socket方式也是一种很好的方式。
  4.还有没有搞定的地方,如果是需要应用在开发中,如何避免selinux禁止appdomain的权限问题,看到网上说可以把socket文件生成在data/app/com.xxx.app/下,回头要好好试试。

参考博客:
Android7.0 PackageManagerService (5) installd
http://blog.csdn.net/Gaugamela/article/details/52769139
Service xxx does not have a SELinux domain defined
http://blog.csdn.net/l460133921/article/details/72891678
android 6.0 Java层和native守护进程socket通信
http://blog.csdn.net/u012439416/article/details/72974388
解决avc-denied之设置SELinux策略
http://blog.csdn.net/eliot_shao/article/details/51859083

阅读全文
0 0
原创粉丝点击