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

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.mk
LOCAL_PATH:= $(call my-dir)
common_src_files := socket_test.cpp
include $(CLEAR_VARS)
LOCAL_SRC_FILES := $(common_src_files)
LOCAL_CFLAGS += -DGL_GLEXT_PROTOTYPES -DEGL_EGLEXT_PROTOTYPES
LOCAL_SHARED_LIBRARIES := \
    libcutils \
    liblog \
    libandroidfw \
    libutils \
    libselinux 

LOCAL_MODULE := socket_test
LOCAL_INIT_RC := socket_test.rc
include $(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 root
adb remount
adb push XX/socket_test.rc system/etc/init/
adb shell
chmod 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/bin
adb 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 root
adb remount
adb shell restorecon system/bin/xxx
adb 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=1

03-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=1

03-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=0
08-06 19:19:29.664 5964-5964/com.demo.mysocketclient W/System.err: java.io.IOException: Permission denied
08-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 root
adb remount
adb 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

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值