博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
进程关系
阅读量:6833 次
发布时间:2019-06-26

本文共 6981 字,大约阅读时间需要 23 分钟。

POSIX规定一个进程内部的多个thread要共享一个PID,

但是,在linux kernel中不论是进程还是线程,都是会分配一个task struct并且分配一个唯一的PID(这时候PID其实就是thread ID)。

这样,为了满足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。

 

我们可以通过如下命令获取进程的状态信息:

# cat /proc/1350/stat   1350 (system_server) S 451 451 0 0 -1 4194624 169945 0 1536 ...

其中第一个数字是pid,S后面的三个数分别是ppid、pgid、sid。

# cat /proc/1350/status                                       Name:    system_serverState:    S (sleeping)Tgid:    1350Pid:    1350PPid:    451TracerPid:    0...

通过上面两个命令,能列出如下几个比较典型的进程之间的关系:

 comm pid ppid tgid pgid sid
init 1 0 1 0 0
kthreadd 2 0 2 0 0
ksoftirqd/0 3 2 3 0 0
zygote64 451 1 451 451 0
zygote 452 1 452 452 0
system_server 1350 451 1350 451 0
PackageManager 1454 451 1350 451 0
com.android.video 7041 452 7041 451 0

0号进程:swapper进程、又名idle进程,内核启动时的第一个执行流。负责初始化内核各个模块,并创建init进程和ktheadd进程,最后进入idle循环,负责idle的管理和cpu热插拔之类的事务。

1号进程:init进程,用户空间的第一个进程,也是所有用户态进程的始祖进程,负责创建和管理各个native进程。

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

ksoftirqd/0:内核线程,只能在内核态执行。

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

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

PackageManager线程:system_server进程里的一个子线程。

com.android.video:普通的一个32位java进程。

 

【pid】

表示一个调度单位task的id:

struct task_struct {    ...    pid_t pid;    ...}

调度单位即执行流,每个执行流都对应一个task_struct。每个task_struct都有唯一的id就是pid。

 

【ppid】

一个task_struct可以创建另一个task_struct,两者有父子关系,ppid就是一个执行流的父执行流。

但这种父子关系并非是绝对的,比如:

static struct task_struct *copy_process(unsigned long clone_flags,...){    ...    /* CLONE_PARENT re-uses the old parent */    if (clone_flags & (CLONE_PARENT|CLONE_THREAD)) {        p->real_parent = current->real_parent;    } else {        p->real_parent = current;    }    ...}

如果创建task_struct时设置了CLONE_THREAD或CLONE_PARENT,

被创建者的父执行流就是当前执行流的父执行流,否则被创建者的父执行流就是当前执行流。

比如PackageManager(1454)是system_server的主线程(1350)创建的,

但1454的ppid不是1350,而是451,也就是1350的父执行流zygote64的主线程。

 

【tgid】

一个或多个线程可以组成一个线程组,线程组内的各个线程会共享地址空间、信号处理函数、文件表舒服等资源。

线程组中主线程的pid就是这个线程组所属线程的tgid。

比如PackageManager(1454)和system_server(1350)同属一个线程组,tgid同为1350。

线程是通过pthread_create()函数创建的:

int pthread_create(pthread_t* thread_out, pthread_attr_t const* attr,  void* (*start_routine)(void*), void* arg) {  ..  int flags = CLONE_VM | CLONE_FS | CLONE_FILES | CLONE_SIGHAND | CLONE_THREAD | CLONE_SYSVSEM |      CLONE_SETTLS | CLONE_PARENT_SETTID | CLONE_CHILD_CLEARTID;  int rc = clone(__pthread_start, child_stack, flags, thread, &(thread->tid), tls, &(thread->tid));  ...}asmlinkage int sys_clone(unsigned long clone_flags, unsigned long newsp,             int __user *parent_tidptr, int tls_val,             int __user *child_tidptr, struct pt_regs *regs){    if (!newsp)        newsp = regs->ARM_sp;    return do_fork(clone_flags, newsp, regs, 0, parent_tidptr, child_tidptr);}long do_fork(unsigned long clone_flags,          unsigned long stack_start,          struct pt_regs *regs,          unsigned long stack_size,          int __user *parent_tidptr,          int __user *child_tidptr){    ...    p = copy_process(clone_flags, stack_start, regs, stack_size, child_tidptr, NULL, trace);    ...}static struct task_struct *copy_process(unsigned long clone_flags,                    unsigned long stack_start,                    struct pt_regs *regs,                    unsigned long stack_size,                    int __user *child_tidptr,                    struct pid *pid,                    int trace){    ...    p->tgid = p->pid;                                 //tgid默认为自己的pid    if (clone_flags & CLONE_THREAD)        p->tgid = current->tgid;                      //tgid为当前线程的tgid    ...}

 

【pgid】

上面说的线程组,其实就是我们常说的进程。

多个进程也能组成组叫进程组,它的领头进程的主线程pid就是pgid。

首先默认情况下,进程组id都是从父进程继承过来的,但是init在创建naitive 服务后会修改pgid。

void service_start(struct service *svc, const char *dynamic_args){    ...    pid_t pid = fork();    if (pid == 0) {  //子进程        ...        setpgid(0, getpid());        ...    }}SYSCALL_DEFINE2(setpgid, pid_t, pid, pid_t, pgid){    struct task_struct *p;    struct task_struct *group_leader = current->group_leader;   //native进程的grop_leader就是自己    struct pid *pgrp;    if (!pid)  //如果传入的pid为0,则        pid = task_pid_vnr(group_leader);    pid = task_pid_vnr(group_leader);    p = find_task_by_vpid(pid);      pgrp = task_pid(p);     if (pgid != pid) {        ...        pgrp = find_vpid(pgid);    }    if (task_pgrp(p) != pgrp)  //原先的pgrp是0号进程,而新的pgrp是当前进程自身        change_pid(p, PIDTYPE_PGID, pgrp);  //修改pgid为当前进程自身    ...}

setpgid(0, getpid())其实就是将自己的pgid设置成自己的pid,

因此所有init创建出来的native进程,他们的pgid就是自身的pid。

如果没有特殊设置,子进程会继承父进程的gpid。这样做有啥用呢?

原来init在收到子进程的退出信号SIGCHLD的后,会直接将子进程所属的进程组里的所有进程杀掉,代码如下:

static bool wait_for_one_process() {    int status;    pid_t pid = TEMP_FAILURE_RETRY(waitpid(-1, &status, WNOHANG));     ...    service* svc = service_find_by_pid(pid);    ...    if (!(svc->flags & SVC_ONESHOT) || (svc->flags & SVC_RESTART)) {        NOTICE("Service '%s' (pid %d) killing any children in process group\n", svc->name, pid);        kill(-pid, SIGKILL);  //这里会杀掉进程组里的所有进程    }}

一般我们用kill系统调用的时,传入的pid是正数,而这里却传入负数。

kill系统调用传正数pid会杀线程组,传负数pid则会杀掉进程组。

相关代码如下:

SYSCALL_DEFINE2(kill, pid_t, pid, int, sig){    struct siginfo info;    info.si_signo = sig;    info.si_errno = 0;    info.si_code = SI_USER;    info.si_pid = task_tgid_vnr(current);    info.si_uid = current_uid();    return kill_something_info(sig, &info, pid);}static int kill_something_info(int sig, struct siginfo *info, pid_t pid){    int ret;    if (pid > 0) {        ret = kill_pid_info(sig, info, find_vpid(pid));  //杀线程组        return ret;    }    if (pid != -1) {        ret = __kill_pgrp_info(sig, info, pid ? find_vpid(-pid) : task_pgrp(current));  //杀进程组    } else {        ...     }    return ret;}

因此zygote创建出来的所有子进程,它的pgid都是zygote的pid,所以zygote挂掉,它的子进程都会被init杀掉。

 

64位下有两个zygote,zygote64和zygote32。

64位应用的父进程是zygote64,它的pgid也是zygote64的pid;

32位应用的父进程是zygote32,它的pgid却是zygote64的pid,这是怎么回事呢?

原来不管32位或64位的zygote,它在创建完子进程后,会调用setChildPgid()来改变子进程的pgid。

如下代码:

private void setChildPgid(int pid) {        try {            Os.setpgid(pid, Os.getpgid(peer.getPid()));        } catch (ErrnoException ex) {            ...        }    }

这里的peer是socket的对端,也就是system_server。而system_server的pgid就是zygote64的pid。

这样,所有zygote32创建出来的子进程,他们的pgid都是zygote64的pid了。

例如:

comm pid ppid tgid pgid sid
com.android.video 7041 452 7041 451 0

com.android.video()的父进程是zygote32(452),但它的pgid是zygote64(451)。

因此如下面的rc:

service zygote_secondary /system/bin/app_process32 -Xzygote /system/bin --zygote --socket-name=zygote_secondary    class main    socket zygote_secondary stream 660 root system    onrestart restart zygote

 当zygote32退出重启时,同时也会重启zygote64,这样zygote32创建出来的进程才能退出。

不过奇怪的进程间关系在某种情况下会有问题,比如:http://www.cnblogs.com/YYPapa/p/6848806.html

 

【sid】

Android中绝大部分情况下都没使用设个sid,绝大部分的sid都是0,只有用sh程序会设置这个sid,这里就不展开了。

 

转载于:https://www.cnblogs.com/YYPapa/p/6853545.html

你可能感兴趣的文章
Cookie和Session(转)
查看>>
纠结的问题
查看>>
SpannableString的一个奇怪的问题
查看>>
乐高情报站7月份抽奖数据汇总。
查看>>
Algorithm Part I:Priority Queues
查看>>
但从谈论性能点SQL Server选择聚集索引键
查看>>
uboot初体验-----趣谈nand设备发起的浅显理解
查看>>
基于selenium的pyse自动化测试框架
查看>>
编译的依赖不能vs的release工程
查看>>
Linux常用的系统监控shell脚本
查看>>
codeforces Gym 100500C D.Hall of Fame 排序
查看>>
约瑟夫环问题
查看>>
yum
查看>>
c++指针存储应用程序和释放内存的问题
查看>>
LPC43xx SGPIO Slice 示意图
查看>>
NUMA的取舍与优化设置
查看>>
uboot源码整体框架
查看>>
编译命令行终端 swift
查看>>
Swift - 使用UISearchController实现带搜索栏的表格
查看>>
web接口测试之GET与POST请求
查看>>