操作系统: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有以下几种:
名称 | 宏定义 | 隔离内容 |
---|---|---|
Cgroup | CLONE_NEWCGROUP | Cgroup root directory (since Linux 4.6) |
IPC | CLONE_NEWIPC | System V IPC, POSIX message queues (since Linux 2.6.19) |
Network | CLONE_NEWNET | Network devices, stacks, ports, etc. (since Linux 2.6.24) |
Mount | CLONE_NEWNS | Mount points (since Linux 2.4.19) |
PID | CLONE_NEWPID | Process IDs (since Linux 2.6.24) |
User | CLONE_NEWUSER | User and group IDs (started in Linux 2.6.23 and completed in Linux 3.8) |
UTS | CLONE_NEWUTS | Hostname 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