1. 程式人生 > >進程間關系

進程間關系

set 意義 會有 gin acer 系統 pthread 線程組 ldr

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.miui.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.miui.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.miui.video 7041 452 7041 451 0

com.miui.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,這裏就不展開了。

進程間關系