进程间关系

Posted YYPapa

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了进程间关系相关的知识,希望对你有一定的参考价值。

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_server
State:    S (sleeping)
Tgid:    1350
Pid:    1350
PPid:    451
TracerPid:    0
...

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

 commpidppidtgidpgidsid
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了。

例如:

commpidppidtgidpgidsid
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,这里就不展开了。

 

以上是关于进程间关系的主要内容,如果未能解决你的问题,请参考以下文章

多线程编程

如何在CEF JS与browser进程间异步通信

进程间通信和线程间通信

Nginx进程间的关系

进程间通信和线程间通信

进程和线程的关系及区别,进程间如何通讯,线程间如何通讯