概述:
本文主要讲解进程基础,更深入的认识有血有肉的进程,内容涉及进程控制块,信号,进程FD泄露等等。仅供参考,欢迎指正。
一、从Linux看进程到底是什么?
“进程四要素” —《Linux 内核源代码情景分析》描述如下:
有一段程序供其执行
拥有专用的系统堆栈空间
在内核存在对应进程控制块
拥有独立的用户存储空间
上面确实有点抽象,进程不仅仅是一段处在执行状态的程序,还包括还文件,信号,内存地址空间,CPU状态等等复杂的信息,更重要的是拥有独立的用户存储空间,这个是与线程的本质区别。在Linux内核中,进程是由一个
下面的代码给出了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.news u0_a159 10276 1112 87 15:19:50 ? 00:00:32 com.ss.android.article.news u0_a159 10731 1112 1 15:19:56 ? 00:00:00 com.ss.android.article.news:pushservice u0_a159 10794 1112 9 15:19:57 ? 00:00:02 com.ss.android.article.news:push u0_a159 10953 1112 2 15:19:58 ? 00:00:00 com.ss.android.article.news:ad shell 11198 11193 6 15:20:27 pts/0 00:00:00 grep com.ss.android.article.news jason:/ $ 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.news State: S (sleeping) Tgid: 10276Pid: 10276PPid: 1112TracerPid: 0Uid: 10159 10159 10159 10159Gid: 10159 10159 10159 10159Ngid: 0FDSize: 512Groups: 3002 3003 9997 20159 50159 VmPeak: 2078244 kB VmSize: 2042672 kB VmLck: 0 kB VmPin: 0 kB VmHWM: 234364 kB VmRSS: 208068 kB VmData: 335236 kB VmStk: 8192 kB VmExe: 20 kB VmLib: 190804 kB VmPTE: 1584 kB VmPMD: 16 kB VmSwap: 0 kB Threads: 142
Tgid是什么:
对于一个多线程的进程来说,它实际上是一个进程组,每个线程在调用getpid()时获取到的是自己的tgid值,而线程组领头的那个领头线程的pid和tgid是相同的
对于独立进程,即没有使用线程的进程来说,它只有唯一一个线程,领头线程,所以它调用getpid()获取到的值就是它的pid
通过上面两个命令可以确认几个常见进程的关系
或许用下面的图表示更直观
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 11364 PID 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-keys Hardware: QC_Reference_Phone Revision: 0Bootloader: unknown Radio: TH20c1.9-0711_1858_6c2ad98 Kernel: 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_crash 10-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.<init>(FileOutputStream.java:221) 10-11 20:46:23.514 1401 1455 E DropBoxManagerService: at java.io.FileOutputStream.<init>(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 window 10-11 20:46:23.614 1401 1473 W WindowManager: java.lang.IllegalArgumentException: Requested window android.view.ViewRootImpl$W@e2ab8e8 does not exist 10-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.news u0_a159 7093 1244 0 11:02:45 ? 00:00:00 com.ss.android.article.news:ad u0_a159 7303 1244 0 11:02:50 ? 00:00:03 com.ss.android.article.news:push u0_a159 7384 1244 0 11:02:51 ? 00:00:01 com.ss.android.article.news:pushservice root 8081 6996 11 11:18:36 pts/0 00:00:00 grep com.ss.android.article.news jason:/ # 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 <<< signal 6 (SIGABRT), code -6 (SI_TKILL), fault addr -------- Abort message: 'Could not make wake event fd: Too many open files' x0 0000000000000000 x1 0000000000005140 x2 0000000000000006 x3 0000000000000008 x4 0000000000000000 x5 0000000000000000 x6 0000000000000000 x7 7f7f7f7f7f7f7f7f x8 0000000000000083 x9 3dbbf71453109433 x10 0000000000000000 x11 0000000000000001 x12 ffffffffffffffff x13 ffffffffffffffff x14 ff00000000000000 x15 ffffffffffffffff x16 000000025c938fa8 x17 000000719e6915c4 x18 0000000000000008 x19 0000000000004f4f x20 0000000000005140 x21 00000070ea2e6800 x22 00000070e5166588 x23 000000711ae3214a x24 0000000000000000 x25 00000070e5166588 x26 00000070ea2e68a0 x27 0000000000000000 x28 0000000000000000 x29 00000070e5164b70 x30 000000719e646e38 sp 00000070e5164b30 pc 000000719e646e54 pstate 0000000060000000 v0 2e2e2e2e2e2e2e2e2e2e2e2e2e2e2e2e v1 2e6761742e676f6c2e74736973726570 v2 00000070e516658800007265706f6f4c v3 00000000000000000000000000000000 v4 80200800000000008020000000000000 v5 40000000400000004000000000000000 v6 00000000000000000000000000000000 v7 80200802802008028020080280200802 v8 00000000000000000000000000000000 v9 00000000000000000000000000000000 v10 00000000000000000000000000000000 v11 00000000000000000000000000000000 v12 00000000000000000000000000000000 v13 00000000000000000000000000000000 v14 00000000000000000000000000000000 v15 00000000000000000000000000000000 v16 40100401401004014010040140100401 v17 a0080000a0000000a800a00040404000 v18 80200800000000008020000000000000 v19 00000000000000000000000000000000 v20 00000000000000000000000000000000 v21 00000000000000000000000000000000 v22 00000000000000000000000000000000 v23 000000000000000000000000433b0000 v24 0000000000000000000000003f800000 v25 0000000000000000000000003f800000 v26 000000000000000000000071a011ebcc v27 000000000000000000000071a011ec24 v28 000000000000000000000071a011ef64 v29 000000000000000000000071a011e5bc v30 000000000000000000000071a011e7b4 v31 000000000000000000000071a011e8e8 fpsr 00000013 fpcr 00000000backtrace: #00 pc 000000000001de54 /system/lib64/libc.so (abort+104) #01 pc 0000000000007f20 /system/lib64/liblog.so (__android_log_assert+304) #02 pc 00000000000154d0 /system/lib64/libutils.so (android::Looper::Looper(bool)+296) #03 pc 00000000001114b8 /system/lib64/libandroid_runtime.so (android::NativeMessageQueue::NativeMessageQueue()+160) #04 pc 0000000000111de8 /system/lib64/libandroid_runtime.so (android::android_os_MessageQueue_nativeInit(_JNIEnv*, _jclass*)+28) #05 pc 00000000009c88e0 /system/framework/arm64/boot-framework.oat (offset 0x9c6000) (android.os.Binder.clearCallingIdentity [DEDUPED]+144) #06 pc 000000000054904c /system/lib64/libart.so (art_quick_invoke_static_stub+604) #07 pc 00000000000dcfb4 /system/lib64/libart.so (art::ArtMethod::Invoke(art::Thread*, unsigned int*, unsigned int, art::JValue*, char const*)+260) #08 pc 000000000029a950 /system/lib64/libart.so (art::interpreter::ArtInterpreterToCompiledCodeBridge(art::Thread*, art::ArtMethod*, art::ShadowFrame*, unsigned short, art::JValue*)+344) #09 pc 0000000000294f40 /system/lib64/libart.so (_ZN3art11interpreter6DoCallILb0ELb0EEEbPNS_9ArtMethodEPNS_6ThreadERNS_11ShadowFrameEPKNS_11InstructionEtPNS_6JValueE+696) #10 pc 0000000000532258 /system/lib64/libart.so (MterpInvokeStatic+224) #11 pc 000000000053ab94 /system/lib64/libart.so (ExecuteMterpImpl+14612) #12 pc 00000000002753a4 /system/lib64/libart.so (art::interpreter::Execute(art::Thread*, art::DexFile::CodeItem const*, art::ShadowFrame&, art::JValue, bool)+444) #13 pc 000000000027afac /system/lib64/libart.so (art::interpreter::ArtInterpreterToInterpreterBridge(art::Thread*, art::DexFile::CodeItem const*, art::ShadowFrame*, art::JValue*)+216) #14 pc 0000000000294f20 /system/lib64/libart.so (_ZN3art11interpreter6DoCallILb0ELb0EEEbPNS_9ArtMethodEPNS_6ThreadERNS_11ShadowFrameEPKNS_11InstructionEtPNS_6JValueE+664) #15 pc 000000000053209c /system/lib64/libart.so (MterpInvokeDirect+304) #16 pc 000000000053ab14 /system/lib64/libart.so (ExecuteMterpImpl+14484) #17 pc 00000000002753a4 /system/lib64/libart.so (art::interpreter::Execute(art::Thread*, art::DexFile::CodeItem const*, art::ShadowFrame&, art::JValue, bool)+444) #18 pc 000000000027afac /system/lib64/libart.so (art::interpreter::ArtInterpreterToInterpreterBridge(art::Thread*, art::DexFile::CodeItem const*, art::ShadowFrame*, art::JValue*)+216) #19 pc 0000000000294f20 /system/lib64/libart.so (_ZN3art11interpreter6DoCallILb0ELb0EEEbPNS_9ArtMethodEPNS_6ThreadERNS_11ShadowFrameEPKNS_11InstructionEtPNS_6JValueE+664) #20 pc 000000000053209c /system/lib64/libart.so (MterpInvokeDirect+304) #21 pc 000000000053ab14 /system/lib64/libart.so (ExecuteMterpImpl+14484) #22 pc 00000000002753a4 /system/lib64/libart.so (art::interpreter::Execute(art::Thread*, art::DexFile::CodeItem const*, art::ShadowFrame&, art::JValue, bool)+444) #23 pc 000000000027afac /system/lib64/libart.so (art::interpreter::ArtInterpreterToInterpreterBridge(art::Thread*, art::DexFile::CodeItem const*, art::ShadowFrame*, art::JValue*)+216) #24 pc 0000000000294f20 /system/lib64/libart.so (_ZN3art11interpreter6DoCallILb0ELb0EEEbPNS_9ArtMethodEPNS_6ThreadERNS_11ShadowFrameEPKNS_11InstructionEtPNS_6JValueE+664) #25 pc 0000000000532258 /system/lib64/libart.so (MterpInvokeStatic+224) #26 pc 000000000053ab94 /system/lib64/libart.so (ExecuteMterpImpl+14612) #27 pc 00000000002753a4 /system/lib64/libart.so (art::interpreter::Execute(art::Thread*, art::DexFile::CodeItem const*, art::ShadowFrame&, art::JValue, bool)+444) #28 pc 0000000000523ab4 /system/lib64/libart.so (artQuickToInterpreterBridge+1052) #29 pc 0000000000551f0c /system/lib64/libart.so (art_quick_to_interpreter_bridge+92) #30 pc 000000000000a080 /dev/ashmem/dalvik-jit-code-cache (deleted)
果然出现了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 <unistd.h>#include <stdio.h>#include <wait.h>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这个变量被原封不动地拷贝到这两个分支之中。
一个进程调用fork()函数后,系统先给新的进程分配资源,例如存储数据和代码的空间。然后把原来的进程的所有值都复制到新的新进程中,只有少数值与原来的进程的值不同。相当于克隆了一个自己。 其实进程的fork基于写时复制技术,相对与传统fork技术更加高效。何为写时复制技术呢?
内核只为新生成的子进程创建虚拟空间结构,它们来复制于父进程的虚拟究竟结构,但是不为这些段分配物理内存,它们共享父进程的物理空间,当父子进程中有更改相应段的行为发生时,再为子进程相应的段分配物理空间。
假设现在有一个进程p1,包括正文段(可重入的程序,能被若干进程共享,比如代码等),数据段(用于保存程序已经初始化的变量),堆,栈。也有文件描述符等。
可以看到传统的fork系统调用直接把父进程所有的资源复制给新创建的进程,如果这时子进程执行exec函数系统调用,那么这种复制毫无意义,在看写时复制技术。
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 <stdio.h>#include <unistd.h>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 <http://gnu.org/licenses/gpl.html>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: <http://www.gnu.org/software/gdb/bugs/>.Find the GDB manual and other documentation resources online at: <http://www.gnu.org/software/gdb/documentation/>.For help, type "help". Type "apropos word" to search for commands related to "word"... Reading symbols from signalApp...done. (gdb) r Starting 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 <<< signal 11 (SIGSEGV), code 1 (SEGV_MAPERR), fault addr 00000000r0 00000000 r1 00000000 r2 000010a0 r3 00000175r4 be9bc150 r5 00000000 r6 be9bbfac r7 4053763d r8 00000174 r9 00001200 sl 00001200 fp 000010e0ip 40664b9c sp be9bbf8c lr 40538701 pc 40128310 cpsr 200f0010 backtrace:#00 pc 00022310 /system/lib/libc.so (memset+24)#01 pc 000b66ff /system/lib/libskia.so (SkDraw::drawPaint(SkPaint const&) const+286)#02 pc 000b1023 /system/lib/libskia.so (SkCanvas::internalDrawPaint(SkPaint const&)+66)#03 pc 000aff65 /system/lib/libskia.so (SkCanvas::drawColor(unsigned int, SkXfermode::Mode)+44)#04 pc 0002034c /system/lib/libdvm.so (dvmPlatformInvoke+112)
从调用栈中可以看出,程序执行到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 <memset+32> 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 <<< signal 11 (SIGSEGV), code 2 (SEGV_ACCERR), fault addr 0x9577b090 r0 00000034 r1 9591b000 r2 00001000 r3 12ced228 r4 b4a7f378 r5 000010e9 r6 b46d73cb r7 000010e9 r8 9577ae70 r9 00000004 sl 00000004 fp b6cc3050 ip 00001000 sp 9577ae10 lr b6c8acf5 pc 9577b090 cpsr 200f0010 backtrace: #00 pc 00102090 [stack:26620] #01 pc 00047cf1 /system/lib/libc.so (__sread+16) #02 pc 000025b4 /system/lib/libart.so (offset 0x44d000)
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的错误,这种一般是函数指针被某一个家伙覆盖导致的。进程信号有很多种,不一一赘述,掌握这些信号的常见场景,需要一定经验和时间。