一 进程是什么?

1.1 什么是进程?

可以认为进程是一个程序的一次执行过程,或者说一个正在执行的程序。在这个过程中,伴随着资源的分配和释放。进程是资源管理的最小单元。

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

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

1.2 进程与程序的区别与联系?

程序是存放在硬盘中的二进制文件(可执行指令的序列),是静态的。而进程是已经加载到主存中,正在执行的程序。它是程序执行的过程,包括创建、调度和消亡。在这个过程中,伴随着资源的分配和释放,是动态的。

1.3 进程在系统中的体现?

1.3.1 可执行文件(elf) 文件的格式


elf 文件中包括很多个字段,当程序执行时需要根据字段信息将程序加载到主存中。其中最重要的两个字段为:

  • 代码段(text)(即可执行的指令)
  • 数据段(data)(运算的中间数据)

1.3.2 进程的内存分布

  • 代码区
    程序(函数)代码所在,由编译而得到的二进制代码被载入至此.代码区是只读的,有执行权限.需要注意的是,字符串字面值(如"Hello World")就存储在这个区
  • 数据段和BSS段
    合称静态区(全局区),用来存储静态(全局)变量.区别是前者(数据段)存储的是已初始化的静态(全局)变量,可读写;后者(BSS段)存储的是未初始化的静态(全局)变量,可读写

  • 自由存储区.不像全局变量和局部变量的生命周期被严格定义,堆区的内存分配和释放是由程序员所控制的

  • 由系统自动分配和释放,存储局部(自动)变量.

1.3.3 进程在系统中的体现

如上述,一个程序,实际上的格式为一个 elf 文件,当执行该程序时,首先按照 elf 文件的各个段,将该程序从硬盘按照上述图表所示加载到主存中,程序存在于一个进程的用户空间,而在内核空间中,维护了一个很大的结构,用于标识该进程的执行的状态。这个结构的名字叫 task_struct,task_struct 又称进程控制块,task_struct 定义在 include/linux/sched.h 文件中摘要如下:

struct task_struct {volatile long state;//进程的状态信息 void *stack;atomic_t usage;unsigned int flags; /* per process flags, defined below */pid_t pid;   //进程的pid是进程的唯一标识pid_t tgid;//线程组的id//如果创建它的父进程不再存在,则指向PID为1的init进程 struct task_struct *real_parent; //parent process “养父进程”通常与real_parent值相同//当它终止时,必须向它的父进程发送信号struct task_struct *parent;struct list_head children;//该进程的孩子进程链表struct list_head sibling;//该进程的兄弟进程链表struct list_head thread_group; //*线程链表 struct task_struct *group_leader;//该进程的线程组长struct timespec start_time;  //进程创建时间 struct fs_struct *fs;  //它包含此进程当前工作目录和根目录//打开的文件相关信息结构体。f_mode字段描述该文件是以什么模式创建的://只读、读写、还是只写。f_pos保存文件中下一个读或写将发生的位置struct files_struct *files;//描述进程的内存使用情况,active_mm指向进程运行时所使用的内存描述符对于普通进程而言//这两个指针变量的值相同,但是内核线程kernel thread是没有进程地址空间的struct mm_struct *mm, *active_mm;//static_prio用于保存静态优先级,prio用于保存动态优先级int prio, static_prio, normal_prio;unsigned int rt_priority;//表示进程的运行优先级/* signal handlers */struct signal_struct *signal;struct sighand_struct *sighand;
}

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,特别强调的是 task_struct 具有双重身份,线程和进程都是用 task_struct 表示,区别在于进程拥有独立的用户空间,而线程和其它线程是共享存储空间的。

1.3.4 进程的状态

task_struct 的 state 域表示进程的状态,有以下几种

  • 可运行态状态(R TASK-RUNNING)
    此时进程正在运行,或者正在运行队列中等待调度。
  • 可中断睡眠态(S TASK_INTERRUPTIBLE)
    此时进程正处于阻塞(睡眠)状态,正在等待某些事件发生或者能够炸弄某些资源。例如设备初始化完成、I/O 操作完成或定时器到时等。处在这种状态下的进程可以被信号中断。进程会在等待的事件发生或者是接收到信号被唤醒,进程转变为(TASK_RUNINNG状态)
  • 不可中断睡眠态(D TASK_UNINTERRUPTIBLE)
    与TASK_INTERRUPTIBLE状态类似,进程处于睡眠状态,但是此刻进程是不可中断的。信号传递给这种状态下的进程不能改变它的状态。只有在它等待的事件发生时,进程才被显示的唤醒。
  • 暂停态(T TASK_STOPPED)
    进程的执行被暂停,当进程收到SIGSTOP、SIGTSTP、SIGTTIN、SIGTTOU等信号,就会进入暂停态。向进程发送一个SIGCONT信号,可以让其从TASK_STOPPED状态恢复到TASK_RUNNING状态
  • 僵尸态 (Z EXIT_ZOMBIE)
    子进程运行结束,但是父进程尚未回收子进程的退出状态。处于这种状态下的进程已经放弃所有了几乎所有的资源,除了task_struct结构(以及少数资源)以外。于是进程就只剩下task_struct这么个空壳,这个task_struct的空壳就形象地被称为僵尸。
  • 消亡态(X EXIT_DEAD)
    父进程回收了子进程了退出状态后,进入的最终状态。这意味着接下来该进程将被彻底释放。所以EXIT_DEAD状态是非常短暂的,几乎不可能通过ps命令捕捉到。

1.4 状态转换图

1.5 进程的运行模式

1.5.1 进程的运行状态

进程的执行模式分为用户模式和内核模式,也称为用户态和内核态。处于用户模式的进程通常情况下只能访问用户空间的虚拟地址,不能访问内核空间的虚拟地址。

注意:
内核空间的是有内核负责映射的,它不会随着进程的改变,是固定的。内核空间地址有自己对应的页表,用户进程各自有不同的页表。每个进程的用户空间都是完全独立、互不相干。

1.5.2 状态切换

用户模式的进程只有通过使用系统调用,或者中断的方式,才能切换到内核模式,访问内核空间。

1.5.3 状态切换举栗子


系统调用其实也是通过中断实现的,系统调用的中断号为 0x80。每个中断会有中断处理函数,系统调用的中断处理函数为 systemcall(),func() 函数会将中断号和系统调用号一起传递给内核。内核根据系统调用号执行系统调用处理函数,例如 open() 函数最终调用的就是 system_open() 系统调用处理函数,然后返回一个文件描述符给用户。

二 进程进阶

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:   10276
Pid:    10276
PPid:   1112
TracerPid:  0
Uid:    10159   10159   10159   10159
Gid:    10159   10159   10159   10159
Ngid:   0
FDSize: 512
Groups: 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
通过上面两个命令可以确认几个常见进程的关系

进程名称 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

用下面的图表示更直观

  • 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_serve r进程 -> 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");}}

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

三 如何创建一个进程

在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:15229
count=10
子进程ID:15230
count=1Process finished with exit code 0

通过打印的结果有两点重要信息需要知道:

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

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

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

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

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

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

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

Android系统启动系列1 进程基础相关推荐

  1. Android系统启动系列----init进程

    Android系统启动系列 Android系统启动系列----init进程 Android系统启动系列----Zygote进程 引言 在开发app的过程中,是不是会有疑问: java程序的运行不是从m ...

  2. Android系统启动流程—— init进程zygote进程SystemServer进程启动流程

    原文地址:https://blog.csdn.net/qq_30993595/article/details/82714409 Android系统启动流程 Android系统启动过程往细了说可以分为5 ...

  3. Android系统启动流程--init进程的启动流程

    这可能是个系列文章,用来总结和梳理Android系统的启动过程,以加深对Android系统相对全面的感知和理解(基于Android11).  1.启动电源,设备上电 引导芯片代码从预定义的地方(固化在 ...

  4. Android系统启动流程4---init进程的工作流程

    概况: 启动init进程 运行init.rc 启动zygote服务 Zygote fork的第一个进程--SystemServer SystemServer启动系统服务 1. linux启动第一个应用 ...

  5. Android 系统启动 <zygote 进程> 笔记【2】

    Read The Fucking Source Code. -- Linus 站在'巨人'的肩膀上开始自己的旅途.-- 佚名 愉快的周末,从打开

  6. Android 系统(243)---Android进程系列第一篇---进程基础

    Android进程系列第一篇---进程基础 内容预览.png 概述: 本文主要讲解进程基础,更深入的认识有血有肉的进程,内容涉及进程控制块,信号,进程FD泄露等等.仅供参考,欢迎指正. 一.从Linu ...

  7. android ss 流程,Android进程系列第一篇---进程基础

    内容预览.png 概述: 本文主要讲解进程基础,更深入的认识有血有肉的进程,内容涉及进程控制块,信号,进程FD泄露等等.仅供参考,欢迎指正. 一.从Linux看进程到底是什么? "进程四要素 ...

  8. Android 10.0 系统启动之SystemServer进程-[Android取经之路]

    摘要:上一节讲解了Zygote进程的整个启动流程.Zygote是所有应用的鼻祖.SystemServer和其他所有Dalivik虚拟机进程都是由Zygote fork而来.Zygote fork的第一 ...

  9. Android 10.0系统启动之init进程-[Android取经之路]

    摘要:init进程是linux系统中用户空间的第一个进程,进程号为1.当bootloader启动后,启动kernel,kernel启动完后,在用户空间启动init进程,再通过init进程,来读取ini ...

最新文章

  1. 在 Swift 中调用 OC 代码
  2. 【STM32】光敏传感器示例
  3. 方立勋_30天掌握JavaWeb_div和css基础
  4. 图书管理员【2017年普及组第二题】
  5. 软工学习笔记——代码规范
  6. botstrap-栅格布局与栅格偏移
  7. ImageUtils.java:图片处理工具类[裁剪/图片水印/文字水印/缩放补白/Base64加密解密]
  8. 计算机与艺术就业怎样,就业报告:这些艺术类好就业,这些难就业!
  9. Android内核开发:系统启动速度优化
  10. python模拟浏览器教程_Python使用win32com实现的模拟浏览器功能示例
  11. 锤子手机T2发布会PPT模板
  12. 硬盘测试软件得分数据怎么看,SSD硬盘测试结果分析怎么看的
  13. Qt那些事儿-Qt基础教程
  14. openbsd停止mysql_英特尔处理器超线程功能被OpenBSD 停用,并爆bug
  15. 【CodeForces】 106C Buns(多重背包)
  16. openlayer判断瓦片全部加载完毕
  17. 我有200台摄像机4MB/s,后端防火墙吞吐量多少G够用?应用层1G够用吗?
  18. h5前端IE浏览器低版本判断及升级提示
  19. access、trunk、vlan
  20. 【案例】如何让阀门制造提高排产效率?APS系统帮你实现

热门文章

  1. itunes备份怎么恢复短信
  2. 浪潮“源”AI大模型如何求解数学应用题
  3. echarts关系图节点点击事件
  4. 数据存储和界面展示二
  5. SIGCHILD信号
  6. 计算机考研英语有听力吗,考研英语试题题型里面有听力吗
  7. 使用red5和OBS搞出自己的直播平台来挑战bilibili(B站)
  8. 凌凯短信Webservice接口报错解决办法
  9. 分享2022流畅运行Solidworks的电脑配置清单
  10. java-四十三天---Vue