Android上层如何调用一个底层函数

1. 背景

本文讲的是调用流程,如何找到相应代码位置,更多的是一种分析代码的方式。

此处将从ZygoteInit调用Zygote.forkSystemServer函数开始跟踪代码直到fork根据父进程和子进程返回pid为止,这会是从上到下的一条简单而通用的例子。

本文的代码是基于Android O的MR0进行分析(kernel仍然是kernel-3.18),MR1正式释放要等到11月吧(kernel google建议是kernel-4.4)

2. Android fork进程的流程

Android要创建一个进程,肯定要调用底层的fork(android 2.3)或者clone(android O)函数,这个意味着上层需要调用到kernel的函数。

这个行为将从JAVA通过JNI调用到C++的代码,再通过C++调用bionic(经过裁剪和优化,为了调用kernel相关内容,可以任何是kernel的GNU接口),然后在syscall到kernel底层函数(以前fork还会经过汇编,目前的这个函数调用并不会)。

我们分为2条线走,一条是Android到bionic,一条是bionic到kernel

2.1 Android到bionic

Android到bionic,我认为是都属于Android的一部分。

2.1.1 ZygoteInit.java启动系统服务

private static boolean startSystemServer(String abiList, String socketName, ZygoteServer zygoteServer)

...

/* Request to fork the system server process */

pid = Zygote.forkSystemServer(

本文不关注具体传递参数,故上述只关注从一个文件调用到另一个文件,以文件结构去梳理

frameworks/base/core/java/com/android/internal/os/ZygoteInit.java 调用到 =>

frameworks/base/core/java/com/android/internal/os/Zygote.java

此处有个pid,就是fork返回的值,众所周知,这个父进程返回的是子进程的pid,子进程返回的是0,后面我们也就是跟踪这一条线到返回pid为止。

2.1.2 Zygote.java创建系统服务进程

public static int forkSystemServer(int uid, int gid, int[] gids, int debugFlags,

...

int pid = nativeForkSystemServer(

...

此处会从java调用到C++的函数,使用的就是类似与JNI的方式,这类行为不在本文阐述范围内

frameworks/base/core/java/com/android/internal/os/Zygote.java 调用到 =>

frameworks/base/core/jni/com_android_internal_os_Zygote.cpp

2.1.3 com_android_internal_os_Zygote.cpp本地函数

//此类函数会注册到虚拟机中

static const JNINativeMethod gMethods[] = {

//第一个是上层java调用函数的名字

//第二个是函数的参数和返回值,这里不会细讲

//第三个就是转换给本地的函数

{ "nativeForkSystemServer", "(II[II[[IJJ)I",

(void *) com_android_internal_os_Zygote_nativeForkSystemServer },

//...

//转换后的本地函数,一般都是以报名字进行拼接

static jint com_android_internal_os_Zygote_nativeForkSystemServer(

....

//内部调用另一个ForkAndSpecializeCommon函数

return ForkAndSpecializeCommon(env, uid, gid, gids, debug_flags,

....

static pid_t ForkAndSpecializeCommon(JNIEnv* env, uid_t uid,

...

//上述其实对于本条流程可以通通不关注,到这里才是重点,调用fork函数,

//注意,此处还不是linux的fork,只是准备调用bionic的fork

pid_t pid = fork();

里面有很多内容,我们直接跳过,还是刚才说的本文主要阐述的是跟踪代码的方式,后续大家可以直接去尝试跟进,根据大的文件目录结构往往都可以找到自己想要的答案(这个大文件架构从android2.3开始就没有特别大的变动)

frameworks/base/core/jni/com_android_internal_os_Zygote.cpp 调用到 =>

bionic/libc/bionic/fork.cpp

2.1.4 fork.cpp用于fork进程

int fork() {

....

int result = clone(nullptr,

....

此处已经是bionic的内容,这里接下去会调用bionic中的clone函数(android 2.3的时候是直接进入fork的汇编)

好了到这里第一部分暂时讲完了,Android如果需要和kernel通信,一般都是需要经过bionic的,如果后续大家看到类似linux的函数可以先在bionic找找,当然你也可以直接在kernel代码中找(如果不关注这些流程,这个会更快,一般这类流程看一遍知道了就不会再看了)

2.2 bionic到kernel的系统调用

接下来需要讲解的是从bionic到kernel的系统调用,还是接着上面的clone函数讲解

* bionic/libc/bionic/fork.cpp 调用到 =>

* bionic/libc/bionic/clone.cpp

2.2.1 clone.cpp克隆进程

int clone(int (*fn)(void*), void* child_stack, int flags, void* arg, ...) {

...

//这里64位和32位很多都是有区分的,此处最后2个参数是经过翻转的

#if defined(__x86_64__) // sys_clone's last two arguments are flipped on x86-64.

clone_result = syscall(__NR_clone, flags, child_stack, parent_tid, child_tid, new_tls);

#else

clone_result = syscall(__NR_clone, flags, child_stack, parent_tid, new_tls, child_tid);

#endif

...

此处syscall的函数是__NR_clone,如果是64位,其定义在kernel-3.18/arch/arm64/include/asm/unistd32.h中

bionic/libc/bionic/clone.cpp 调用到 =>

kernel-3.18/arch/arm64/include/asm/unistd32.h

2.2.2 unistd32.h系统调用定义

//kernel-3.18/arch/arm64/include/asm/unistd32.h

__SYSCALL(__NR_clone, sys_clone)

很简单的一句话__SYSCALL,其实际代表的意思是__NR_clone这个函数就是sys_clone,具体我们还得看看__SYSCALL的定义

具体定时在kernel-3.18/arch/arm64/kernel/sys.c中 =>

//kernel-3.18/arch/arm64/kernel/sys.c

#define __SYSCALL(nr, sym) [nr] = sym,

好了接下去我们就需要看sys_clone这个函数具体是哪里调用的,我们搜索整份源代码发现,并未有以sys_clone开通的函数,那它跑去哪里了呢?

2.2.3 syscalls.h系统调用的头文件

//kernel-3.18/include/linux/syscalls.h

#define SYSCALL_DEFINE1(name, ...) SYSCALL_DEFINEx(1, _##name, __VA_ARGS__)

#define SYSCALL_DEFINE2(name, ...) SYSCALL_DEFINEx(2, _##name, __VA_ARGS__)

#define SYSCALL_DEFINE3(name, ...) SYSCALL_DEFINEx(3, _##name, __VA_ARGS__)

#define SYSCALL_DEFINE4(name, ...) SYSCALL_DEFINEx(4, _##name, __VA_ARGS__)

#define SYSCALL_DEFINE5(name, ...) SYSCALL_DEFINEx(5, _##name, __VA_ARGS__)

#define SYSCALL_DEFINE6(name, ...) SYSCALL_DEFINEx(6, _##name, __VA_ARGS__)

由于已经知道后续具体调用的就是SYSCALL_DEFINE5(clone…,这个提前跟大家说一下,第一次查看代码可以通过反推,后面就没有必要了。

注意##代表链接符,”_##name”对于clone来说,就是”_clone”

所以我们先来看看里面的一堆SYSCALL_DEFINE+1,2,3…这样的函数,类似于SYSCALL_DEFINE5(name, …)其宏定义是SYSCALL_DEFINEx(5, _##name, VA_ARGS),也就是下面类似的=>

#define SYSCALL_DEFINEx(x, sname, ...) \

//SYSCALL_METADATA这个东西暂时无需关注,定义一些syscall的数据结构,此处先关注函数定义

SYSCALL_METADATA(sname, x, __VA_ARGS__) \

__SYSCALL_DEFINEx(x, sname, __VA_ARGS__)

上面转换SYSCALL_DEFINEx为__SYSCALL_DEFINEx,这个也是一个宏定义=>

#define __SYSCALL_DEFINEx(x, name, ...) \

asmlinkage long sys##name(__MAP(x,__SC_DECL,__VA_ARGS__)) \

__attribute__((alias(__stringify(SyS##name)))); \

...

好吧上面又多了2个宏__MAP和__SC_DECL,不过终于我们看到函数名字了sys##name。针对sys_clone函数来说,SYSCALL_DEFINE5(clone的函数名字其实和sys_clone是一样的。

顺便看一下__MAP和__SC_DECL的宏定义

//__SC_DECL这个宏只是将里面的2个参数变成一前一后的形式而已,这需要结合__MAP一起看

#define __SC_DECL(t, a) t a

//__MAP(x的意思就是调用__MAPx而已,__MAPx其实就是将每2个参数组合成一个,英文解释也比较清楚

//__MAP(n, m, t1, a1, t2, a2, ..., tn, an) will expand to

// m(t1, a1), m(t2, a2), ..., m(tn, an)

#define __MAP0(m,...)

#define __MAP1(m,t,a) m(t,a)

#define __MAP2(m,t,a,...) m(t,a), __MAP1(m,__VA_ARGS__)

#define __MAP3(m,t,a,...) m(t,a), __MAP2(m,__VA_ARGS__)

#define __MAP4(m,t,a,...) m(t,a), __MAP3(m,__VA_ARGS__)

#define __MAP5(m,t,a,...) m(t,a), __MAP4(m,__VA_ARGS__)

#define __MAP6(m,t,a,...) m(t,a), __MAP5(m,__VA_ARGS__)

#define __MAP(n,...) __MAP##n(__VA_ARGS__)

类似与SYSCALL_DEFINE5(clone通过上述转换=>

//宏定义原型

SYSCALL_DEFINE5(clone, unsigned long, clone_flags, unsigned long, newsp,

int __user *, parent_tidptr,

int __user *, child_tidptr,

int, tls_val)

=>

SYSCALL_DEFINEx(5, _clone, __VA_ARGS__)

=>

__SYSCALL_DEFINEx(5, _clone, __VA_ARGS__)

=>

sys_clone(__MAP(5,__SC_DECL,__VA_ARGS__))

=>两两凑对,共5个参数

sys_clone(unsigned long clone_flags, unsigned long newsp,

int __user * parent_tidptr,

int __user * child_tidptr,

int tls_val

这里和 2.2.1 的syscall(__NR_clone, flags, child_stack, parent_tid, child_tid, new_tls)函数调用传递的参数是一样的,都是5个

2.2.4 fork.c具体的fork函数

我们接下去看看

kernel-3.18/kernel/fork.c

接着刚才的SYSCALL_DEFINE5(clone,接下去调用的是do_fork=>

SYSCALL_DEFINE5(clone, unsigned long, clone_flags, unsigned long, newsp,

int __user *, parent_tidptr,

int __user *, child_tidptr,

int, tls_val)

{

return do_fork(clone_flags, newsp, 0, parent_tidptr, child_tidptr);

}

do_fork函数是实际fork进程的地方,里面包含copy_process拷贝父进程的相关信息、唤醒子进程wake_up_new_task、返回子进程的进程pid值=>

long do_fork(unsigned long clone_flags,

unsigned long stack_start,

unsigned long stack_size,

int __user *parent_tidptr,

int __user *child_tidptr)

{

struct task_struct *p;

...

//通过copy_process,将父进程的信息全部拷贝一份到子进程,还会指定子进程开始运行的位置

p = copy_process(clone_flags, stack_start, stack_size,

child_tidptr, NULL, trace);

...

if (!IS_ERR(p)) {

...

//get_task_pid拿的值也是task->pids[PIDTYPE_PID].pid,这个在init_task_pid有传递给

//task_struct,这些讲的都是上面copy_process创建的pid

pid = get_task_pid(p, PIDTYPE_PID);

nr = pid_vnr(pid);//这个就是进程id值

...

wake_up_new_task(p);//唤醒子进程,将子进程推入调度队列中

...

return nr;

}

到这里返回值nr就是父进程传递给上层的进程号(pid)。do_fork这个函数完整的执行过程只会在父进程运行,子进程不会进入do_fork这个代码的任何一个位置,pid_vnr用来获取子进程的pid并且返回=>

pid_t pid_vnr(struct pid *pid)

{

return pid_nr_ns(pid, task_active_pid_ns(current));

}

pid_t pid_nr_ns(struct pid *pid, struct pid_namespace *ns)

{

struct upid *upid;

pid_t nr = 0;

//ns->level,pid->level都是0,pid是copy_process创建的,下面会讲到

if (pid && ns->level <= pid->level) {

upid = &pid->numbers[ns->level];//在copy_process的alloc_pid时传递的是父进程命名空间

if (upid->ns == ns)//命名空间的指针是一样的

nr = upid->nr;//但是pid是alloc_pid时新建的

}

return nr;

}

上述父进程的分析已经完了,父进程就是返回自己的pid。关于子进程的返回,我们不得不看一下copy_process的内容,这个函数将会把父进程的相关信息都拷贝一份到子进程中=>

static struct task_struct *copy_process(unsigned long clone_flags,

...

//新建一个task_struct,用于存放子进程的数据

p = dup_task_struct(current);

...

//调度相关的初始化,设置子进程状态TASK_RUNNING,分配CPU

retval = sched_fork(clone_flags, p);

...

//拷贝寄存器相关,此处将设置子进程开始运行的地方

retval = copy_thread(clone_flags, stack_start, stack_size, p);

...

if (pid != &init_struct_pid) {

retval = -ENOMEM;

pid = alloc_pid(p->nsproxy->pid_ns_for_children);//为子进程分配pid,与其命名空间

}

...

设置子进程的pid

p->pid = pid_nr(pid);

...

init_task_pid(p, PIDTYPE_PID, pid);//设置子进程task_struct中PIDTYPE_PID的值是上面创建的pid

...

return p;//返回拷贝的子进程task_struct

}

跟子进程返回相关的函数copy_thread拷贝寄存器相关时在proces.c中

代码位置kernel-3.18/arch/arm64/kernel/proces.c

2.2.5 proces.c的copy_thread

我们接下去看看copy_thread

int copy_thread(unsigned long clone_flags, unsigned long stack_start,

unsigned long stk_sz, struct task_struct *p)

{

struct pt_regs *childregs = task_pt_regs(p);//拷贝寄存器

...

*childregs = *current_pt_regs();

childregs->regs[0] = 0;//一般寄存器0对于返回函数代表的是返回值,此处fork返回值是0

...

//设置pc指针,这个代表的是进程运行CPU指针,也就是子进程开始运行的地方是ret_from_fork

p->thread.cpu_context.pc = (unsigned long)ret_from_fork;

//设置sp,传递寄存器相关值到sp,如子进程fork的返回值就包含在里面

p->thread.cpu_context.sp = (unsigned long)childregs;

...

}

至于ret_from_fork这个函数就没有必要看进去了。

子进程在do_fork的wake_up_new_task以后开始进入调度队列的列表中,一开始执行的地方就是ret_from_fork,不会运行do_fork的任何代码,至于返回值是直接设定为0.

到这里基本上从上到下的调用关系澄清,后续有相关底层调用可以直接搜索底层函数,没有必要再次查看流程,一般搜索方式是 =>

grep -rn "(clone," .

其中clone是你要搜索的函数名字,如你要搜索sys_clone,那么可以直接使用上述指令

linux底层把值传给上层,Android上层如何调用一个底层函数相关推荐

  1. 【Android】JNI调用(完整版)

    原文出处:http://blog.csdn.net/kangyaping/article/details/6584027#t0 Chap1:JNI完全手册... 3 Chap2:JNI-百度百科... ...

  2. uinput 用法 android 上层使用uinput 的用法来模拟 input 事件

    android 上层使用uinput 的用法来模拟 input 事件 #ifndef _GNU_SOURCE #define _GNU_SOURCE #endif#include <stdio. ...

  3. android上层应用apk到G-sensor driver的大致流程

    android上层应用apk到G-sensor driver的大致流程: Android HAL层,即硬件抽象层,是Google响应厂家"希望不公开源码"的要求推出的新概念 1,源 ...

  4. Android上层与驱动交互完整篇(一)I2C设备驱动篇

    Android上层与驱动交互完整篇(一) 驱动篇 以I2C设备驱动为例,创建与上层交互节点,解析数据并与设备进行通讯. kernel中编程如同站在巨人肩膀上,有时候,我们并不需要理解I2C总线是如何工 ...

  5. 安卓底层linux开发教程,Android手机平台移植与底层开发 PDF

    支持Android的热门ARM硬件平台简介 ARMv7架构SOC l TI OMAP系列,2009年OMAP3和OMAP4关注度较高 l Freescale i.MX51系列 l Qualcomm 8 ...

  6. 安卓linux输入代码在哪里,输入  |  Android 开源项目  |  Android Open Source Project

    Android 输入子系统名义上由遍历系统多个层的事件管道组成. 输入管道 在最低层,物理输入设备会生成描述状态更改(例如按键按压和轻触接触点)的信号.设备固件以某种方式编码和传输这些信号,例如向系统 ...

  7. Android 8.0 linux内核,在Ubuntu上为Android增加硬件抽象层(HAL)模块访问Linux内核驱动程序---Android8.0版本实现-对照老罗版本...

    老罗版本参见:https://blog.csdn.net/luoshengyang/article/details/6573809 在Android硬件抽象层(HAL)概要介绍和学习计划一文中,我们简 ...

  8. linux文件系统启动流程,linux 内核启动过程以及挂载android 根文件系统的过程

    转载 作者:汕头大学-黄珠唐 时间:2009 年10 月29 日 主要介绍linux 内核启动过程以及挂载android 根文件系统的过程,以及介绍android 源代码中文件系统部分的浅析. 主要源 ...

  9. 优秀博客链接(linux c/c++ java go php android ios 前端 j2ee windows linux 算法 ACM 深度/机器学习 AI opencv nlp)

    pudn 阿甘兄 前端 服务端 底层 移动端 大数据 云计算 AI 培训机构的课程差不多就这一套了 大数据 AI NLP 高等数学 LeetCode.<数据结构与算法之美>学习笔记.AI ...

最新文章

  1. docker 占用磁盘空间清理 无用数据卷删除
  2. QTableView中点击单元格弹出QComboBox
  3. surefire 拉起testng单元测试类的源码流程阅读(二)
  4. 登录页面跳出框架的JS
  5. PWN-PRACTICE-BUUCTF-1
  6. flowable 实现多实例-会签-动态配置人员 参考demo
  7. php 抽象类 静态方法吗,php中的抽象类和静态方法是什么
  8. 安装了dns且可以正常工作为什么还有没有可以使用的DNS服务器的错误
  9. 18号是什么php,19年1月18号CSS浮动float
  10. Java编写斗地主的游戏源码
  11. NTP时间服务器安装配置详解
  12. WebView打不开或者显示异常可能原因
  13. JS获取ul中li的值同步到搜索框
  14. c#中regex的命名空间_C#_详解C#正则表达式Regex常用匹配,使用Regex类需要引用命名空间 - phpStudy...
  15. Abaqus应力结点数据导出与处理
  16. linux trace 进程 文件路径,linux panic 问题定位
  17. css获取父元素下第几个元素出坑和JQuery通过index()获取下标出坑方法
  18. Oracle 小数格式化字符串显示 (转)
  19. 初学Simcenter Flotherm XT划分网格和气流
  20. 基于java的企业人事管理系统设计--软件工程课程设计(含源码与论文设计).rar

热门文章

  1. 消防产品在酒店行业的应用
  2. BeanPostProcessor妙用(转载)
  3. 我为Bill Gates熬夜加班的那个晚上
  4. 3D打印将对零售模式产生颠覆影响,能否抓住机遇
  5. vue尚品汇商城项目-day00【项目介绍:此项目是基于vue2的前台电商项目和后台管理系统】
  6. 星巴克季节限定星怡杯樱花味拿铁升级回归
  7. 缔造评测领先品牌的鲁大师,近日将有一个大举措
  8. AMD显卡安装Caffe|深度学习|Ubuntu
  9. 特殊儿童领间最灿烂的一缕红——我们入队了
  10. java添加窗体中_java中利用JFrame创建窗体 【转】