進程間關系
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: 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.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了。
例如:
comm | pid | ppid | tgid | pgid | sid |
---|---|---|---|---|---|
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,這裏就不展開了。
進程間關系