(三)进程各种id:pid、pgid、sid、全局pid、局部pid

操作系统:linux
处理器:arm
内核版本:4.x

目录:

进程作为系统资源,有着各种标识,错综复杂,下面就来简单的理一理。

1.从linux出发

enum pid_type
{
	PIDTYPE_PID,
	PIDTYPE_PGID,
	PIDTYPE_SID, 
	PIDTYPE_MAX, 
	/* only valid to __task_pid_nr_ns() */
	__PIDTYPE_TGID
};  

pid

PID即process id,通过fork、vfork、clone产生的新进程都会分配一个新的pid,独一无二。

只有gettid系统调用时才会返回进程的pid:

SYSCALL_DEFINE0(gettid)
{                                                                         
	return task_pid_vnr(current);                               
}  

tgid

tgid即thread group id。进程的tgid就是其group_leader的pid,对于没有使用线程的情况,tgid等于自身的pid。使用clone系统调用时,如果使用CLONE_THREAD标志,那么group_leader就会指向父进程,处于同一个线程组的所有进程他们的tgid都相同。

用户眼中的pid(getpid)其实并不是真正的pid,而是tgid!!!

SYSCALL_DEFINE0(getpid)
{
	return task_tgid_vnr(current); 
} 

static inline pid_t task_tgid_vnr(struct task_struct *tsk)
{  
	return __task_pid_nr_ns(tsk, __PIDTYPE_TGID, NULL);
}             

pid_t __task_pid_nr_ns(struct task_struct *task, enum pid_type type,
						struct pid_namespace *ns)
{
	pid_t nr = 0;                                                           
                                                                                
	rcu_read_lock();                                                        
	if (!ns)                                                                
		ns = task_active_pid_ns(current);                               
		if (likely(pid_alive(task))) {                                          
			if (type != PIDTYPE_PID) {                                      
				if (type == __PIDTYPE_TGID)                             
				type = PIDTYPE_PID;                             
				task = task->group_leader;                              
			}                                                               
		nr = pid_nr_ns(rcu_dereference(task->pids[type].pid), ns);      
	}                                                                       
	rcu_read_unlock();                                                      
                                                                
	return nr;                                                              
}                                                                               
EXPORT_SYMBOL(__task_pid_nr_ns);  

可以看到传入__PIDTYPE_TGID类型时,最终返回其group_leader的pid。


pgid

进程可以组成进程组(setpgrp系统调用),进程组可以简化向所有组内进程发送信号的操作。进程组ID叫做PGID,进程组内的所有进程都有相同的PGID,等于该组组长的PID。

SYSCALL_DEFINE1(getpgid, pid_t, pid)
{
	struct task_struct *p;
	struct pid *grp;                                                                                                                                                                                                                                                                                                                                                                                                                                              
	int retval;

    rcu_read_lock();                                                                                      
    if (!pid)                                                                                             
        grp = task_pgrp(current);                                                                     
    else {                                                                                                
        retval = -ESRCH;                                                                              
        p = find_task_by_vpid(pid);                                                                   
        if (!p)                                                                                       
        goto out;                                                                             
        grp = task_pgrp(p);                                                                           
        if (!grp)                                                                                     
        goto out;                                                                             

        retval = security_task_getpgid(p);                              
        if (retval)                                                                                   
        goto out;                                                                             
    }                                                                                                     
    retval = pid_vnr(grp);                                                                                
out:                                                                                                          
    rcu_read_unlock();                                                                                    
    return retval;                                                                                        
}          

sid

几个进程组可以合并成一个会话组(使用setsid系统调用),可以用于终端程序设计。会话组中所有进程都有相同的SID。

SYSCALL_DEFINE1(getsid, pid_t, pid)                                                                           
{                                                                                                             
	struct task_struct *p;                                                                                
	struct pid *sid;                                                                                      
	int retval;                                                                                           
                                                                                                              
	rcu_read_lock();                                                                                      
	if (!pid)                                                                                             
		sid = task_session(current);                                                                  
	else {                                                                                                
		retval = -ESRCH;                                                                              
		p = find_task_by_vpid(pid);                                                                   
		if (!p)                                                                                       
		goto out;                                                                             
		sid = task_session(p);                                                                        
		if (!sid)                                                                                     
		goto out;                                                                             
                                                                                                               
		retval = security_task_getsid(p);                                                             
		if (retval)                                                                                   
			goto out;                                                                             
	}                                                                                                     
	retval = pid_vnr(sid);                                                                                
out:                                                                                                          
	rcu_read_unlock();                                                                                    
	return retval;                                                                                        
}            

2.其他

tid

tid在linux上就是pid!执行gettid调用时返回的就是pid。


spid

spid即systen pid,也是pid,ps命令中的pid实际上是tgid。
这里写图片描述


ppid

父进程的pid。

SYSCALL_DEFINE0(getppid)                                                                                      
{                                                                                                             
	int pid;                                                                                              
                                                                                                              
	rcu_read_lock();                                                                                      
	pid = task_tgid_vnr(rcu_dereference(current->real_parent));             
	rcu_read_unlock();                                                                                    
                                                                                                              
	return pid;                                                                                           
}                      

3.pid命令空间

Namespace是对全局系统资源的一种封装隔离,使得处于不同namespace的进程拥有独立的全局系统资源,改变一个namespace中的系统资源只会影响当前namespace里的进程,对其他namespace中的进程没有影响。

目前linux内核里已经支持的namespace有以下几种:

名称宏定义隔离内容
CgroupCLONE_NEWCGROUPCgroup root directory (since Linux 4.6)
IPCCLONE_NEWIPCSystem V IPC, POSIX message queues (since Linux 2.6.19)
NetworkCLONE_NEWNETNetwork devices, stacks, ports, etc. (since Linux 2.6.24)
MountCLONE_NEWNSMount points (since Linux 2.4.19)
PIDCLONE_NEWPIDProcess IDs (since Linux 2.6.24)
UserCLONE_NEWUSERUser and group IDs (started in Linux 2.6.23 and completed in Linux 3.8)
UTSCLONE_NEWUTSHostname and NIS domain name (since Linux 2.6.19)

查看进程所属的namespace:

root@ubuntu:/work# ls /proc/237/ns/ -l
total 0
lrwxrwxrwx 1 root root 0 Aug  4 19:04 cgroup -> 'cgroup:[4026531835]'
lrwxrwxrwx 1 root root 0 Aug  4 19:04 ipc -> 'ipc:[4026531839]'
lrwxrwxrwx 1 root root 0 Aug  4 19:04 mnt -> 'mnt:[4026531840]'
lrwxrwxrwx 1 root root 0 Aug  4 19:04 net -> 'net:[4026531993]'
lrwxrwxrwx 1 root root 0 Aug  4 19:04 pid -> 'pid:[4026531836]'
lrwxrwxrwx 1 root root 0 Aug  4 19:04 pid_for_children -> 'pid:[4026531836]'
lrwxrwxrwx 1 root root 0 Aug  4 19:04 user -> 'user:[4026531837]'
lrwxrwxrwx 1 root root 0 Aug  4 19:04 uts -> 'uts:[4026531838]'

跟namespace相关的API:

//创建一个新的进程并把他放到新的namespace中
int clone(int (*child_func)(void *), void *child_stack, int flags, void *arg);
//将当前进程加入到已有的namespace中
int setns(int fd, int nstype);
//使当前进程退出指定类型的namespace,并加入到新创建的namespace(相当于创建并加入新的namespace)
int unshare(int flags);

演示pid_namespace:

root@ubuntu:/home/win9# unshare --uts --pid --mount --fork /bin/bash
root@ubuntu:/home/win9# hostname container-test
root@ubuntu:/home/win9# exec bash
root@container-test:/home/win9# ls
Desktop  Documents  Downloads  Music  Pictures  Public  Templates  Videos  a.out  examples.desktop  main.c  snap
root@container-test:/home/win9# mount -t proc proc /proc
root@container-test:/home/win9# ps aux
USER        PID %CPU %MEM    VSZ   RSS TTY      STAT START   TIME COMMAND
root          1  0.0  0.0  18640  3488 pts/6    S    22:28   0:00 bash
root         24  0.0  0.0  34400  2920 pts/6    R+   22:29   0:00 ps aux


引入pid namespace

a long time ago,内核不支持pid_namespace的时候,pid和tgid都是直接存放在task_struct中。
这里写图片描述
PID namespaces使得不同pid namespace里的进程ID可以重复且相互之间不影响,可以嵌套。在当前namespace里面创建的所有新的namespace都是当前namespace的子namespace,父namespace里面可以看到所有子孙后代namespace里的进程信息,而子namespace里看不到祖先或者兄弟namespace里的进程信息。

目前PID namespace最多可以嵌套32层,由内核中的宏MAX_PID_NS_LEVEL来定义。

为了支持pid_namespace,内核增加了pid和upid结构体。pid结构体中的tasks是链表头,指向task_struct中pid_link的node,可以把一个进程相关的process group task和session task给串起来。

struct pid                                                                                                    
{                                                                                                             
	atomic_t count;                                                                                       
	unsigned int level;                                                                                   
	/* lists of tasks that use this pid */                                                                
	struct hlist_head tasks[PIDTYPE_MAX];                                                                 
	struct rcu_head rcu;                                                                                  
	struct upid numbers[1];                                                                               
};

struct upid {                                                                                                 
	/* Try to keep pid_chain in the same cacheline as nr for find_vpid */   
	int nr;                                                                                               
	struct pid_namespace *ns;                                                                             
	struct hlist_node pid_chain;                                                                          
};                                                                                                                                                                                                                           

upid中nr即是pid,可以看到pid结构体中默认自带level0的upid。

怎么通过upid中的nr找到task_struct?

如果具有高层级的upid,多出的upid会一次排列在pid结构体之后。根据upid所在pid_namespace的level变量,利用upid中pid号和container_of宏就可以找到pid结构体,从而找到task_struct。过程如下所示。

struct pid *find_pid_ns(int nr, struct pid_namespace *ns)
{
    struct hlist_node *elem;
    struct upid *pnr;

    hlist_for_each_entry_rcu(pnr, elem,&pid_hash[pid_hashfn(nr, ns)], pid_chain)     
        if (pnr->nr == nr && pnr->ns == ns)     
            return container_of(pnr, struct pid,numbers[ns->level]);

    return NULL;
}

全局pid、局部pid

这里写图片描述
全局pid就是处于level0的upid中nr值,局部pid就是除level0外upid中nr的值。对于只有level0的pid namespace,局部pid就是全局pid。


pid_hash表

pid_hash表是一种加快查找速度的结构体。
这里写图片描述

哈希表以 键-值(key-indexed) 存储数据的结构,输入待查找的值即key,即可查找到其对应的值。通过hash函数,输入pid就可以找到在hash表中对应的位置,如果在hash表中位置相同,那么就可以将他们串起来。通过调整hash函数算法和hash表的大小,就可以在时间和空间上找到一个平衡点。

这里用到的hash函数就是pid_hashfn,pid_hash中存放的是链表头,指向upid中的pid_chain。对于不同level但pid号相同的upid可能会被挂到同一串pid_chain,所以通过pid号查找pid结构体的情况下需要指定namespace。


4.相关API

                                                                             
task_xid_nr()     : global id, i.e. the id seen from the init namespace;  
    
task_xid_vnr()    : virtual id, i.e. the id seen from the pid namespace of current.                                
                
task_xid_nr_ns()  : id seen from the ns specified;                           
                                                                               
set_task_vxid()   : assigns a virtual id to a task;                          
  
pid_vnr()、 pid_vnr()、pid_nr_ns()和上面意义相同。                                                                                                        

参考文章:
Linux 内核进程管理之进程ID

  • 6
    点赞
  • 36
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值