Android进程系列第一篇---进程基础

 

Android 系统(243)---Android进程系列第一篇---进程基础_第1张图片

内容预览.png

概述:

本文主要讲解进程基础,更深入的认识有血有肉的进程,内容涉及进程控制块,信号,进程FD泄露等等。仅供参考,欢迎指正。

一、从Linux看进程到底是什么?

“进程四要素” —《Linux 内核源代码情景分析》描述如下:

  • 有一段程序供其执行
  • 拥有专用的系统堆栈空间
  • 在内核存在对应进程控制块
  • 拥有独立的用户存储空间

上面确实有点抽象,进程不仅仅是一段处在执行状态的程序,还包括还文件,信号,内存地址空间,CPU状态等等复杂的信息,更重要的是拥有独立的用户存储空间,这个是与线程的本质区别。在Linux内核中,进程是由一个task_struct 的结构体表示的,task_struct又称进程控制块,task_struct 定义在include/linux/sched.h文件中。
下面的代码给出了task_struct的一小部分,已经在后面写清楚了注释,这个结构体相对较大,在32位的机器上约1.7kb。

struct task_struct {   volatile long state;    //进程的允许状态/* -1 unrunnable, 0 runnable, >0 stopped */    void *stack;   atomic_t usage;   unsigned int flags; /* per process flags, defined below */   pid_t pid;   //进程的pid   pid_t tgid;//线程组的id   struct task_struct *real_parent; /* real parent process (when being debugged)  ,如果创建它的父进程不再存在,则指向PID为1的init进程*/     struct task_struct *parent; /* parent process “养父进程”通常与real_parent值相同,当它终止时,必须向它的父进程发送信号*/     struct list_head children;  /* list of my children */该进程的孩子进程链表   struct list_head sibling;   /* linkage in my parent's children list */ 该进程的兄弟进程链表   struct list_head thread_group; //*线程链表    struct task_struct *group_leader;   /* threadgroup leader */该进程的线程组长   struct timespec start_time;  //进程创建时间    struct fs_struct *fs;  //文件系统信息    struct files_struct *files;//打开的文件信息   struct mm_struct *mm, *active_mm;//描述进程的地址空间,active_mm指向进程运行时所使用的内存描述符, 对于普通进程而言,这两个指针变量的值相同,但是内核线程kernel thread是没有进程地址空间的   int prio, static_prio, normal_prio;//static_prio用于保存静态优先级,prio用于保存动态优先级   unsigned int rt_priority;//用于保存实时优先级   /* signal handlers */   struct signal_struct *signal;   struct sighand_struct *sighand;}

state表示进程的状态定义如下,我们了解其中几种。

/** Task state bitmask. NOTE! These bits are also* encoded in fs/proc/array.c: get_task_state().** We have two separate sets of flags: task->state* is about runnability, while task->exit_state are* about the task exiting. Confusing, but this way* modifying one set can't modify the other one by* mistake.*/#define TASK_RUNNING        0#define TASK_INTERRUPTIBLE  1#define TASK_UNINTERRUPTIBLE    2#define __TASK_STOPPED      4#define __TASK_TRACED       8/* in tsk->exit_state */#define EXIT_DEAD       16#define EXIT_ZOMBIE     32#define EXIT_TRACE      (EXIT_ZOMBIE | EXIT_DEAD)/* in tsk->state again */#define TASK_DEAD       64#define TASK_WAKEKILL       128#define TASK_WAKING     256#define TASK_PARKED     512#define TASK_STATE_MAX      1024
状态 含义
TASK_RUNNING 0 表示进程要么正在执行,要么正要准备执行(就绪状态),正在等待cpu时间片的调度
TASK_INTERRUPTIBLE 1 进程因为等待一些条件而被挂起(阻塞)而所处的状态。这些条件主要包括:硬中断、资源、一些信号……,一旦等待的条件成立,进程就会从该状态迅速转化成为TASK_RUNNING状态,是一种可中断的等待状态
TASK_UNINTERRUPTIBLE 2 不可中断的等待状态
TASK_STOPPED 4 进程被停止执行,当进程接收到SIGSTOP、SIGTTIN、SIGTSTP或者SIGTTOU信号之后就会进入该状态
TASK_TRACED 8 表示进程被debugger等进程监视,进程执行被调试程序所停止,当一个进程被另外的进程所监视,每一个信号都会让进城进入该状态
EXIT_ZOMBIE 32 进程被终止,但是其父进程还没有使用wait()等系统调用来获知它的终止信息,此时进程成为僵尸进程
EXIT_DEAD 16 进程的最终状态

POSIX(Portable Operating System Interface for Computing System,准确地说是针对类Unix操作系统的标准化协议)规定一个进程内部的多个thread要共享一个PID,在很多情况下,进程都是动态分配一个 task_struct 表示,其实线程也是由一个task_struct 来表示的,所以task_struct具有双重身份,既可以作为进程对象,也可以作线程对象。这样,为了满足POSIX的线程规定,linux引入了线程组的概念,一个进程中的所有线程所共享的那个PID被称为线程组ID,也就是task struct中的tgid成员,因此,在linux kernel中,线程组ID(tgid,thread group id)就是传统意义的进程ID。对于getpid系统调用,linux内核返回了tgid。对于gettid系统调用,本意是要求返回线程ID,在linux内核中,返回了task struct的pid成员。简单来一句总结:POSIX的进程ID就是linux中的线程组ID。POSIX的线程ID也就是linux中的pid

小结:
1、进程控制块的数据结构,主要包含下列信息

a、进程的标志符,如pid,uid等
b、进程的状态,如state, exit_state, prio等
c、进程间的关系,如parent, children,sibling等
d、进程拥有的资源,如mm,fs,files,mm等
e、信号处理函数,如signal, sighand等

2、task_struct具有双重身份,线程和进程都是用task_struct表示,区别在于进程拥有独立的用户空间,而线程和其它线程是共享存储空间的。

二、进程进阶

2.1、 pid ,ppid ,tgid ,pgid ,sid 的理解

上面了解了进程的数据结构,我们可以通过下面两条命令来查看进程的信息,进一步加强进程相关标识的理解(pid ,ppid ,tgid ,pgid ,sid )

cat /proc/self/status cat /proc/self/stat 

拿头条App举例

jason:/ $ ps -ef |grep com.ss.android.article.newsu0_a159      10276  1112 87 15:19:50 ?    00:00:32 com.ss.android.article.newsu0_a159      10731  1112 1 15:19:56 ?     00:00:00 com.ss.android.article.news:pushserviceu0_a159      10794  1112 9 15:19:57 ?     00:00:02 com.ss.android.article.news:pushu0_a159      10953  1112 2 15:19:58 ?     00:00:00 com.ss.android.article.news:adshell        11198 11193 6 15:20:27 pts/0 00:00:00 grep com.ss.android.article.newsjason:/ $ cat /proc/10276/stat                                                                                                                                                                             10276 (id.article.news) S 1112 1111 0 0 -1 1077936448 365859 144549 137 0 3783 3165 1004 406 16 -4 144 0 31842 2094268416 53986 18446744073709551615 1 1 0 0 0 0 4612 1 1073779960 0 0 0 17 2 0 0 0 0 0 0 0 0 0 0 0 0 0

每个参数意思为:
pid=10276 进程(包括轻量级进程,即线程)号
comm=id.article.news 应用程序或命令的名字
task_state=S 任务的状态,R:running, S:sleeping, D:disk T: stopped, T:tracing stop,Z:zombie, X:dead
ppid=1112 父进程ID
pgid=1111 Process Group ID 进程组 ID号
sid=0 该任务所在的会话组ID
TODO:内存中进程是怎么组织的
.....

pgid是什么:每个进程都会属于一个进程组(process group),每个进程组中可以包含多个进程。进程组会有一个进程组领导进程 (process group leader),领导进程的PID成为进程组的ID (process group ID, PGID),以识别进程组。

sid是什么:更进一步,在shell支持工作控制(job control)的前提下,多个进程组还可以构成一个会话 (session),sid标识会话id,Android中进程的sid基本都是0。

130|jason:/ $ cat /proc/10276/status                                                                                                                                                                       Name:   id.article.newsState:  S (sleeping)Tgid:   10276Pid:    10276PPid:   1112TracerPid:  0Uid:    10159   10159   10159   10159Gid:    10159   10159   10159   10159Ngid:   0FDSize: 512Groups: 3002 3003 9997 20159 50159 VmPeak:  2078244 kBVmSize:  2042672 kBVmLck:         0 kBVmPin:         0 kBVmHWM:    234364 kBVmRSS:    208068 kBVmData:   335236 kBVmStk:      8192 kBVmExe:        20 kBVmLib:    190804 kBVmPTE:      1584 kBVmPMD:        16 kBVmSwap:        0 kBThreads:    142

Tgid是什么:

  1. 对于一个多线程的进程来说,它实际上是一个进程组,每个线程在调用getpid()时获取到的是自己的tgid值,而线程组领头的那个领头线程的pid和tgid是相同的
  2. 对于独立进程,即没有使用线程的进程来说,它只有唯一一个线程,领头线程,所以它调用getpid()获取到的值就是它的pid

通过上面两个命令可以确认几个常见进程的关系

进程名称 pid ppid tgid pgid sid
init 1 0 1 1 0
kthreadd 2 0 2 0 0
zygote64 1111 1 1111 1111 0
zygote 1112 1 1112 1112 0
system_server 1735 1111 1735 1111 0
com.ss.android.article.news 10276 1112 10276 1111 0

或许用下面的图表示更直观

 

Android 系统(243)---Android进程系列第一篇---进程基础_第2张图片

进程关系.png

  • 1号进程:init进程,用户空间的第一个进程,也是所有用户态进程的始祖进程,负责创建和管理各个native进程。也有0号线程,swapper进程、又叫idle进程,它创建了init进程和ktheadd进程。

  • 2号进程:kthreadd进程,内核线程的始祖进程,负责创建ksoftirqd/0等内核线程。

  • zygote进程:init创建的,有64位和32位两种,所有的java进程都是由他们孵化而来,他们是所有java进程的父进程。

  • system_server进程:Android的核心进程,1735号线程是其主线程

  • com.ss.android.article.news:普通的一个32位java进程。

从表格中列举的关系,可看到一个Android的App进程进程的创建过程,是由idle进程 -> init进程 -> zygote进程 -> system_server进程 -> App进程。

问题:64位下有两个zygote,zygote64和zygote。64位应用的父进程是zygote64,它的pgid也是zygote64的pid;32位应用的父进程是zygote,它的pgid却是zygote64的pid,如:com.ss.android.article.news的父进程是zygote(1112),但它的pgid是zygote64(1111),这是怎么回事呢?原来不管32位或64位的zygote,它在创建完子进程后,会调用setChildPgid()来改变子进程的pgid。

   private void setChildPgid(int pid) {       // Try to move the new child into the peer's process group.       try {           Os.setpgid(pid, Os.getpgid(peer.getPid()));       } catch (ErrnoException ex) {           // This exception is expected in the case where           // the peer is not in our session           // TODO get rid of this log message in the case where           // getsid(0) != getsid(peer.getPid())           Log.i(TAG, "Zygote: setpgid failed. This is "               + "normal if peer is not in our session");       }   }

peerpeer是socket的对端,也就是system_server。而system_server的pgid就是zygote64的pid。这样,所有zygote32创建出来的子进程,他们的pgid都是zygote64的pid了。

2.2、进程内存信息的查看

除了用上面的cat /proc/pid/status可以查看内存之外,也可以用 top -p pid。比如你写了一个占用内存的程序,想看看占用的内存对不对,就可以使用top -p pid。

top -p 11364PID USER         PR  NI VIRT  RES  SHR S[%CPU] %MEM     TIME+ ARGS                                                                                                                                        11364 root         20   0 1.7G  44K  24K S  0.0   0.0   2:14.29 ramServer 40

VIRT为1.7G,这个是虚拟内存,举个列子,malloc只是申请了虚拟空间,并没有占用物理内存。​只有使用时才会分配物理内存,如memset申请的那块区域时才会分配物理内存。实际占用的内存可以参考RES的大小。

2.3、进程的fd泄露问题

进程的fd泄露问题一直是一个很难搞的问题,先看一段system_server_crash的trace吧

1970-10-11 20:47:05 SYSTEM_RESTART (text, 342 bytes)Build: Xiaomi/scorpio/scorpio:7.0/NRD90M/7.7.24:user/release-keysHardware: QC_Reference_PhoneRevision: 0Bootloader: unknownRadio: TH20c1.9-0711_1858_6c2ad98Kernel: Linux version 3.18.31-perf-g0bf156d-00846-gadfe339 (builder@c3-miui-ota-bd28.bj) (gcc version 4.9 20150123 (prerelease) (GCC) ) #1 SMP PREEMPT Mon Jul 24 06:01:35 CST 201710-11 20:46:23.514 1401 1455 E DropBoxManagerService: Can't write: system_server_crash10-11 20:46:23.514 1401 1455 E DropBoxManagerService: java.io.FileNotFoundException: /data/system/dropbox/drop19.tmp (Too many open files)10-11 20:46:23.514 1401 1455 E DropBoxManagerService: at java.io.FileOutputStream.open(Native Method)10-11 20:46:23.514 1401 1455 E DropBoxManagerService: at java.io.FileOutputStream.(FileOutputStream.java:221)10-11 20:46:23.514 1401 1455 E DropBoxManagerService: at java.io.FileOutputStream.(FileOutputStream.java:169)10-11 20:46:23.514 1401 1455 E DropBoxManagerService: at com.android.server.DropBoxManagerService.add(DropBoxManagerService.java:250)10-11 20:46:23.514 1401 1455 E DropBoxManagerService: at com.android.server.DropBoxManagerService$2.add(DropBoxManagerService.java:129)10-11 20:46:23.514 1401 1455 E DropBoxManagerService: at android.os.DropBoxManager.addText(DropBoxManager.java:282)10-11 20:46:23.514 1401 1455 E DropBoxManagerService: at com.android.server.am.ActivityManagerService$24.run(ActivityManagerService.java:14245)10-11 20:46:23.514 1401 1455 E DropBoxManagerService: at com.android.server.am.ActivityManagerService.addErrorToDropBox(ActivityManagerService.java:14252)10-11 20:46:23.514 1401 1455 E DropBoxManagerService: at com.android.server.am.ActivityManagerService.handleApplicationCrashInner(ActivityManagerService.java:13813)10-11 20:46:23.514 1401 1455 E DropBoxManagerService: at com.android.server.am.ActivityManagerService.handleApplicationCrash(ActivityManagerService.java:13797)10-11 20:46:23.514 1401 1455 E DropBoxManagerService: at com.android.internal.os.RuntimeInit$UncaughtHandler.uncaughtException(RuntimeInit.java:176)10-11 20:46:23.514 1401 1455 E DropBoxManagerService: at java.lang.ThreadGroup.uncaughtException(ThreadGroup.java:1068)10-11 20:46:23.514 1401 1455 E DropBoxManagerService: at java.lang.ThreadGroup.uncaughtException(ThreadGroup.java:1063)10-11 20:46:23.614 1401 1473 W WindowManager: Failed looking up window10-11 20:46:23.614 1401 1473 W WindowManager: java.lang.IllegalArgumentException: Requested window android.view.ViewRootImpl$W@e2ab8e8 does not exist10-11 20:46:23.614 1401 1473 W WindowManager: at com.android.server.wm.WindowManagerService.windowForClientLocked(WindowManagerService.java:9300)10-11 20:46:23.614 1401 1473 W WindowManager: at com.android.server.wm.WindowManagerService.windowForClientLocked(WindowManagerService.java:9291)10-11 20:46:23.614 1401 1473 W WindowManager: at com.android.server.wm.WindowManagerService.removeWindow(WindowManagerService.java:2411)10-11 20:46:23.614 1401 1473 W WindowManager: at com.android.server.wm.Session.remove(Session.java:193)10-11 20:46:23.614 1401 1473 W WindowManager: at android.view.ViewRootImpl.dispatchDetachedFromWindow(ViewRootImpl.java:3324)10-11 20:46:23.614 1401 1473 W WindowManager: at android.view.ViewRootImpl.doDie(ViewRootImpl.java:5938)10-11 20:46:23.614 1401 1473 W WindowManager: at android.view.ViewRootImpl.die(ViewRootImpl.java:5915)10-11 20:46:23.614 1401 1473 W WindowManager: at android.view.WindowManagerGlobal.removeViewLocked(WindowManagerGlobal.java:452)10-11 20:46:23.614 1401 1473 W WindowManager: at android.view.WindowManagerGlobal.removeView(WindowManagerGlobal.java:390)10-11 20:46:23.614 1401 1473 W WindowManager: at android.view.WindowManagerImpl.removeViewImmediate(WindowManagerImpl.java:126)10-11 20:46:23.614 1401 1473 W WindowManager: at com.android.server.policy.PhoneWindowManager.addStartingWindow(PhoneWindowManager.java:2735)10-11 20:46:23.614 1401 1473 W WindowManager: at com.android.server.wm.WindowManagerService$H.handleMessage(WindowManagerService.java:8290)10-11 20:46:23.614 1401 1473 W WindowManager: at android.os.Handler.dispatchMessage(Handler.java:102)10-11 20:46:23.614 1401 1473 W WindowManager: at android.os.Looper.loop(Looper.java:160)10-11 20:46:23.614 1401 1473 W WindowManager: at android.os.HandlerThread.run(HandlerThread.java:61)10-11 20:46:23.614 1401 1473 W WindowManager: at com.android.server.ServiceThread.run(ServiceThread.java:46)10-11 20:46:23.615 1401 1455 W ActivityManager: Force-killing crashed app null at watcher's request

看到Log中有Too many open files,这就是FD泄露,默认每一个进程最多能够打开的文件数量为1024, 一旦达到预置,再试图打开一个文件时就会出错, 即Too many open files。如果看到这个异常,我们可以用lsof命令,对这个进程打开的所有文件进行监控:比如查看头条的进程打开的文件数量,这样下次发生的时候,就知道FD泄露的具体位置是在哪里了。

jason:/ # ps -ef |grep com.ss.android.article.newsu0_a159       7005  1244 5 11:02:44 ?     00:00:44 com.ss.android.article.newsu0_a159       7093  1244 0 11:02:45 ?     00:00:00 com.ss.android.article.news:adu0_a159       7303  1244 0 11:02:50 ?     00:00:03 com.ss.android.article.news:pushu0_a159       7384  1244 0 11:02:51 ?     00:00:01 com.ss.android.article.news:pushserviceroot          8081  6996 11 11:18:36 pts/0 00:00:00 grep com.ss.android.article.newsjason:/ # jason:/ # jason:/ # jason:/ # lsof -p 7005                                                                                                                                                                                     COMMAND     PID       USER   FD      TYPE             DEVICE  SIZE/OFF       NODE NAMEid.articl  7005    u0_a159  cwd       DIR                0,1         0          1 /id.articl  7005    u0_a159  rtd       DIR                0,1         0          1 /id.articl  7005    u0_a159  txt       REG             259,37     29116        781 /system/bin/app_process32id.articl  7005    u0_a159  mem       REG             259,37     29116        781 /system/bin/app_process32id.articl  7005    u0_a159  mem   unknown                                         /dev/ashmem/dalvik-main space (region space) (deleted)id.articl  7005    u0_a159  mem       REG              252,0     90112    1851444 /data/dalvik-cache/arm/system@framework@boot.artid.articl  7005    u0_a159  mem       REG              252,0     12288    1851445 /data/dalvik-cache/arm/system@framework@boot-QPerformance.artid.articl  7005    u0_a159  mem       REG              252,0   2543616    1851446 /data/dalvik-cache/arm/system@framework@boot-core-oj.artid.articl  7005    u0_a159  mem       REG              252,0   1191936    1851447 /data/dalvik-cache/arm/system@framework@boot-core-libart.artid.articl  7005    u0_a159  mem       REG              252,0    315392    1851448 /data/dalvik-cache/arm/system@framework@boot-conscrypt.artid.articl  7005    u0_a159  mem       REG              252,0    192512    1851449 /data/dalvik-cache/arm/system@framework@boot-okhttp.artid.articl  7005    u0_a159  mem       REG              252,0    413696    1851450 /data/dalvik-cache/arm/system@framework@boot-bouncycastle.artid.articl  7005    u0_a159  mem       REG              252,0    446464    1851451 /data/dalvik-cache/arm/system@framework@boot-apache-xml.artid.articl  7005    u0_a159  mem       REG              252,0     20480    1851452 /data/dalvik-cache/arm/system@framework@boot-legacy-test.artid.articl  7005    u0_a159  mem       REG              252,0    385024    1851453 /data/dalvik-cache/arm/system@framework@boot-ext.artid.articl  7005    u0_a159  mem       REG              252,0   9338880    1851454 /data/dalvik-cache/arm/system@framework@boot-framework.artid.articl  7005    u0_a159  mem       REG              252,0    831488    1851455 /data/dalvik-cache/arm/system@framework@boot-telephony-common.artid.articl  7005    u0_a159  mem       REG              252,0     53248    1851456 /data/dalvik-cache/arm/system@framework@boot-voip-common.artid.articl  7005    u0_a159  mem       REG              252,0     57344    1851457 /data/dalvik-cache/arm/system@framework@boot-ims-common.artid.articl  7005    u0_a159  mem       REG              252,0    204800    1851458 /data/dalvik-cache/arm/system@framework@boot-org.apache.http.legacy.boot.artid.articl  7005    u0_a159  mem       REG              252,0      8192    1851459 /data/dalvik-cache/arm/system@framework@boot-android.hidl.base-V1.0-java.artid.articl  7005    u0_a159  mem       REG              252,0     16384    1851460 /data/dalvik-cache/arm/system@framework@boot-android.hidl.manager-V1.0-java.artid.articl  7005    u0_a159  mem       REG              252,0      8192    1851461 /data/dalvik-cache/arm/system@framework@boot-tcmiface.artid.articl  7005    u0_a159  mem       REG              252,0     12288    1851462 /data/dalvik-cache/arm/system@framework@boot-telephony-ext.artid.articl  7005    u0_a159  mem       REG              252,0     36864    1851463 /data/dalvik-cache/arm/system@framework@boot-WfdCommon.artid.articl  7005    u0_a159  mem       REG              252,0     12288    1851464 /data/dalvik-cache/arm/system@framework@boot-oem-services.artid.articl  7005    u0_a159  mem       REG              252,0     45056    1851465 /data/dalvik-cache/arm/system@framework@boot-qcom.fmradio.artid.articl  7005    u0_a159  mem       REG              252,0    516096    1851466 /data/dalvik-cache/arm/system@framework@boot-miui.artid.articl  7005    u0_a159  mem       REG              252,0    290816    1851467 /data/dalvik-cache/arm/system@framework@boot-miuisystem.artid.articl  7005    u0_a159  mem       REG             259,37    212088       1978 /system/framework/arm/boot.oatid.articl  7005    u0_a159  mem       REG             259,37     26020       1909 /system/framework/arm/boot-QPerformance.oatid.articl  7005    u0_a159  mem       REG             259,37  12007484       1933 /system/framework/arm/boot-core-oj.oat.....

含义:

COMMAND:进程的名称
PID:进程标识符
USER:进程所有者
FD:文件描述符,应用程序通过文件描述符识别该文件。如cwd、txt等
TYPE:文件类型,如DIR、REG等
DEVICE:指定磁盘的名称
SIZE:文件的大小
NODE:索引节点(文件在磁盘上的标识)
NAME:打开文件的确切名称

除了打开文件会申请fd之外,每打开一个socket都会增加一个fd,每次创建一个线程也会打开一个fd。系统中经常会有fd泄露的问题存在,所以O上发生NE时会将fd信息打印到tombstone文件中。对于JAVA的,只有自己想办法监控了。下面模拟一下FD泄露。

有些厂商会自己修改Max open files的值,比如华为,在小米手机上仍然是1024,我们可以在prop/pid/limits中查看。

2|jason:/proc/32194 # cat limits                                                                                                                                                                           Limit                     Soft Limit           Hard Limit           Units     Max cpu time              unlimited            unlimited            seconds   Max file size             unlimited            unlimited            bytes     Max data size             unlimited            unlimited            bytes     Max stack size            8388608              unlimited            bytes     Max core file size        0                    unlimited            bytes     Max resident set          unlimited            unlimited            bytes     Max processes             22019                22019                processes Max open files            1024                 4096                 files     Max locked memory         67108864             67108864             bytes     Max address space         unlimited            unlimited            bytes     Max file locks            unlimited            unlimited            locks     Max pending signals       22019                22019                signals   Max msgqueue size         819200               819200               bytes     Max nice priority         40                   40                   Max realtime priority     0                    0                    Max realtime timeout      unlimited            unlimited            us        

现在可以模拟一个fd泄露的问题,代码如下:

public class MainActivity extends Activity {   @Override   protected void onCreate(Bundle savedInstanceState) {       super.onCreate(savedInstanceState);       setContentView(R.layout.activity_main);       View view = findViewById(R.id.open_thread);       view.setOnClickListener(new View.OnClickListener() {           @Override           public void onClick(View view) {               for(int i=0;i<1024;i++){                   HandlerThread   mWorkHandler = new HandlerThread("workHandlerThread");                   mWorkHandler.start();               }           }       });   }}

运行之后,几秒就crash了,抓一份284Log(或者去data/tombstone中),看一下trace。

*** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***Build fingerprint: 'Xiaomi/jason/jason:8.1.0/OPM1.171019.019/8.6.20:user/release-keys'Revision: '0'ABI: 'arm64'pid: 20303, tid: 20800, name: workHandlerThre  >>> com.example.wangjing.rebootdemo <<

果然出现了Could not make wake event fd: Too many open files。

pid: 20303, tid: 20800, name: workHandlerThre  >>> com.example.wangjing.rebootdemo <<<

现在就需要去代码中查找workHandlerThread是什么了。

三、如何创建一个进程

在linux中可以使用fork()来创建一个进程,来看下函数的定义以及返回值,函数原型 pid_t fork(void)
函数返回值: 0: 子进程 , -1: 出错, >0: 父进程

#include #include #include int main() {   int count = 0;   pid_t fpid = fork();   if (fpid < 0) {       printf("创建父子进程失败!");   } else if (fpid == 0) {       printf("子进程ID:%d\n", getpid());       count++;   } else {       printf("父进程ID:%d\n", getpid());       count=10;   }   printf("count=%d\n", count);   waitpid(fpid, NULL, 0);   return 0;}
/home/wangjing/CLionProjects/untitled/cmake-build-debug/untitled父进程ID:15229count=10子进程ID:15230count=1Process finished with exit code 0

通过打印的结果有两点重要信息需要get。

  • 1、fork函数执行一次,返回两次,第一次返回父进程的id,第二次返回子进程的id。
  • 2、count是全局变量,子进程和父进程同时操作,但是互相不受影响

利用fork()函数将整个程序分成了两半,在pid_t fpid==0是子进程执行的分支,大于0则是父进程执行的分支。 count=0这个变量被原封不动地拷贝到这两个分支之中。

 

Android 系统(243)---Android进程系列第一篇---进程基础_第3张图片

fork原理.png

一个进程调用fork()函数后,系统先给新的进程分配资源,例如存储数据和代码的空间。然后把原来的进程的所有值都复制到新的新进程中,只有少数值与原来的进程的值不同。相当于克隆了一个自己。 其实进程的fork基于写时复制技术,相对与传统fork技术更加高效。何为写时复制技术呢?

内核只为新生成的子进程创建虚拟空间结构,它们来复制于父进程的虚拟究竟结构,但是不为这些段分配物理内存,它们共享父进程的物理空间,当父子进程中有更改相应段的行为发生时,再为子进程相应的段分配物理空间。

假设现在有一个进程p1,包括正文段(可重入的程序,能被若干进程共享,比如代码等),数据段(用于保存程序已经初始化的变量),堆,栈。也有文件描述符等。

 

Android 系统(243)---Android进程系列第一篇---进程基础_第4张图片

传统的fork技术.png

可以看到传统的fork系统调用直接把父进程所有的资源复制给新创建的进程,如果这时子进程执行exec函数系统调用,那么这种复制毫无意义,在看写时复制技术。

Android 系统(243)---Android进程系列第一篇---进程基础_第5张图片

写时复制技术.png

fork()之后父进程的将自己的虚拟空间拷贝给子进程,使得子进程可以共享父进程的物理空间,节省了很多物理内存。等到子进程需要写的时候,内核会为子进程分配数据段,堆,栈等,而正文段段继续共享父进程的。很显然,基于写时复制,进程的创建会更加高效。

四、进程信号

进程间的通信除了上层所说的socket,binder,管道等等,还可以用信号来交流,信号是在软件层次上对中断机制的一种模拟,在原理上,一个进程收到一个信号与处理器收到一个中断请求可以说是一样的。系统支持的所有信号如下所示:

4.1信号种类

Android系统中的信号我们可以用 adb shell kill -l 查看

wangjing@wangjing-OptiPlex-7050:~$ adb shell kill -l 1    HUP Hangup                        33     33 Signal 33 2    INT Interrupt                     34     34 Signal 34 3   QUIT Quit                          35     35 Signal 35 4    ILL Illegal instruction           36     36 Signal 36 5   TRAP Trap                          37     37 Signal 37 6   ABRT Aborted                       38     38 Signal 38 7    BUS Bus error                     39     39 Signal 39 8    FPE Floating point exception      40     40 Signal 40 9   KILL Killed                        41     41 Signal 4110   USR1 User signal 1                 42     42 Signal 4211   SEGV Segmentation fault            43     43 Signal 4312   USR2 User signal 2                 44     44 Signal 4413   PIPE Broken pipe                   45     45 Signal 4514   ALRM Alarm clock                   46     46 Signal 4615   TERM Terminated                    47     47 Signal 4716 STKFLT Stack fault                   48     48 Signal 4817   CHLD Child exited                  49     49 Signal 4918   CONT Continue                      50     50 Signal 5019   STOP Stopped (signal)              51     51 Signal 5120   TSTP Stopped                       52     52 Signal 5221   TTIN Stopped (tty input)           53     53 Signal 5322   TTOU Stopped (tty output)          54     54 Signal 5423    URG Urgent I/O condition          55     55 Signal 5524   XCPU CPU time limit exceeded       56     56 Signal 5625   XFSZ File size limit exceeded      57     57 Signal 5726 VTALRM Virtual timer expired         58     58 Signal 5827   PROF Profiling timer expired       59     59 Signal 5928  WINCH Window size changed           60     60 Signal 6029     IO I/O possible                  61     61 Signal 6130    PWR Power failure                 62     62 Signal 6231    SYS Bad system call               63     63 Signal 6332     32 Signal 32                     64     64 Signal 64

4.1信号的产生

我们可以使用$ adb shell kill -{signum} {pid}给对应的进程发送信号,如果遇到系统卡死,需要抓取system_server的trace,就可以使用kill -3。

 adb shell kill  -3  systemserver_pid

生成的system_server trace在data/anr/traces.txt中。
在APP发送Native Crash或者系统发送Native Crash的时候,会发送对应的信号,一个有经验的程序员就会知道这种信号的意思。

写一个系统发送信号的小例子

#include #include int main(){    char *str = "signal";    *str = 'a';     printf("%s\n", str);    return 0;}

gcc编译成可运行的程序

gcc signal.c -g -o signalApp 

开始运行

wangjing@wangjing-OptiPlex-7050:~/桌面$ ./signalApp 段错误 (核心已转储)

发现错误,开始调试

wangjing@wangjing-OptiPlex-7050:~/桌面$ gdb signalApp GNU gdb (Ubuntu 7.11.1-0ubuntu1~16.5) 7.11.1Copyright (C) 2016 Free Software Foundation, Inc.License GPLv3+: GNU GPL version 3 or later This is free software: you are free to change and redistribute it.There is NO WARRANTY, to the extent permitted by law.  Type "show copying"and "show warranty" for details.This GDB was configured as "x86_64-linux-gnu".Type "show configuration" for configuration details.For bug reporting instructions, please see:.Find the GDB manual and other documentation resources online at:.For help, type "help".Type "apropos word" to search for commands related to "word"...Reading symbols from signalApp...done.(gdb) rStarting program: /home/wangjing/桌面/signalApp Program received signal SIGSEGV, Segmentation fault.0x000000000040053a in main () at signal.c:66       *str = 'a'; (gdb) 

可以看到信号是SIGSEGV(11),这是一个段错误,11我们最常见的信号,分SEGV_MAPERR和SEGV_ACCERR两种。第一种是SEGV_MAPERR,意为地址不在进程地址空间内时触发,比如:

pid: 1219, tid: 1219, name: ndroid.systemui >>> com.android.systemui <<

从调用栈中可以看出,程序执行到memset+24的位置时,cpu发现异常。我们可以通过gdb或者objdump工具查看这个汇编:

(gdb) disassemble 0x401282f8Dump of assembler code for function memset:   0x401282f8 <+0>:    stmfd    sp!, {r0}   0x401282fc <+4>:    vdup.8    q0, r1   0x40128300 <+8>:    subs    r2, r2, #32   0x40128304 <+12>:    bcc    0x4012e318    0x40128308 <+16>:    vorr    q1, q0, q0   0x4012830c <+20>:    subs    r2, r2, #32   0x40128310 <+24>:    vst1.8    {d0-d3}, [r0]!            <<<<

可以看到是把d0-d3寄存器的值写到r0寄存器指向的地址时发生的异常。我们可以从r0寄存器的值可以知道,这个地址是0x00000000,而0x00000000不在进程地址空间范围内,所以会引起SEGV_MAPERR错误。"fault addr 00000000" 这个信息也能说明问题,但我们不看汇编不能确定是哪个寄存器(r0和r1都有可能)。看了汇编后能确定是r0,也就是memset的第一个参数为空导致了这个问题。

第二种为SEGV_ACCERR,意为地址在进程地址空间内,但访问权限不够时触发,比如

Build fingerprint: 'Xiaomi/virgo/virgo:6.0.1/MMB29M/7.1.19-internal:user/test-keys'Revision: '0'ABI: 'arm'pid: 26620, tid: 26867, name: DetectorThread  >>> com.linecorp.b612.android <<

fault addr 0x9577b090, SEGV_ACCERR错误指的是访问权限的问题,如写只读段,或者是执行数据段的内容.寄存器的值中发现PC是pc 9577b090,那么有可能是地址9577b090所处的段的属性有问题.查看tombstone中的maps信息:

    95678000-95678fff ---         0      1000--->95679000-9577bfff rw-         0    103000  [stack:26620]    9577c000-9578cfff rw-     a9000     11000  /dev/kgsl-3d0

0x9577b090处在区间[95679000,9577bfff]内,这段地址是有rw权限的,少的是x权限,也就是可执行权限。所以抛出了SEGV_ACCERR的错误,这种一般是函数指针被某一个家伙覆盖导致的。进程信号有很多种,不一一赘述,掌握这些信号的常见场景,需要一定经验和时间。

Relevant Link:
linux内核数据结构学习总结
The Linux Process Principle,NameSpace, PID、TID、PGID、PPID、SID、TID、TTY
https://www.cnblogs.com/wuchanming/p/4495479.html

更多相关文章

  1. Android中的线程模型,
  2. [转载]Android中的线程模型
  3. android异步线程利用Handler将消息发送至UI线程
  4. Android进程保活-自“裁”或者耍流氓
  5. Android消息机制原理,仿写Handler Looper源码跨线程通信原理--之
  6. Android进程学习
  7. Android UI 线程更新UI也会崩溃???
  8. Android跨进程通信IPC之15——Binder之native层C++篇--注册服务

随机推荐

  1. Android(安卓)ImageLoader 本地缓存
  2. Android(安卓)正则表达式 匹配 (数字)x(
  3. android中TextView实现单行跑马灯
  4. Android:异步调用详解
  5. Android类说明---MeasureSpec
  6. Android(安卓)跨应用调用Activity及Servi
  7. Android下图片处理的的一些方法
  8. Android 应用安全风险与防范
  9. Android的UI设计与后台线程交互
  10. Android的SQLite学习及使用方法(1)