• 进程关系


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

  • 相关阅读:
    死锁分析-(DML+DDL触发Server层死锁)
    archery 1.80推送工单到飞书webhook失败解决方案
    mysql执行计划 Select tables optimized away
    apparmor mysql_Ubuntu 上更改 MySQL 数据库数据存储目录
    查看docker容器的tcp连接(转)
    Mongo副本集搭建方式
    数据库字段命名方法
    C# DataGridview控件自动下拉到最后一行
    C# datagridview绑定List<string>显示的是数据长度
    DataGridView 清空数据
  • 原文地址:https://www.cnblogs.com/YYPapa/p/6853545.html
Copyright © 2020-2023  润新知