1)复现流程及lxc的处理

demo1程序与执行结果如下,此时在容器内部看不到执行的程序。

int main()
{int ret, fd, pid;printf("father pid old:%d\n", getpid());fd = open("/dev/ns", O_RDWR);ret = ioctl(fd, 24635); // parm is dst ns process's pidprintf("father pid old:%d\n", getpid());sleep(5);return 0;
}
# ./a.out
father pid old:26169
father pid old:26169

demo2程序与执行结果如下,此时容器内还是看不到执行程序,但是这里getpid()获取到的值就为0了,对比上边,世界上没有这么玄乎的事,或许是缓存的问题?这是第一个疑问。

int main()
{int ret, fd, pid;fd = open("/dev/ns", O_RDWR);ret = ioctl(fd, 24635); // parm is dst ns process's pidprintf("father pid old:%d\n", getpid());sleep(5);return 0;
}
# ./a.out
father pid old:0

demo3程序与执行结果如下,这时在容器内部能看到子进程,为什么子进程实现了pid ns的切换,父进程却没有实现?这是第二个疑问。

int main()
{int ret, fd, pid;fd = open("/dev/ns", O_RDWR);ret = ioctl(fd, 24635); // parm is dst ns process's pidprintf("father pid old:%d\n", getpid());pid = fork();if(0 == pid) {printf("son pid:%d\n", getpid());sleep(5);} else {printf("father pid:%d\n", getpid());wait();}return 0;
}
# ./a.out
father pid old:0
father pid:0
son pid:87PID USER      PR  NI    VIRT    RES    SHR S  %CPU %MEM     TIME+ COMMAND                                         1 root      20   0   22036   2144   1624 S   0.0  0.1   0:00.01 bash                                            57 root      20   0   23672   1516   1160 R   0.0  0.1   0:00.66 top                                             87 root      20   0    3760    204    120 S   0.0  0.0   0:00.00 a.out

lxc attach流程如下,实现思路和我们上边的测试demo一致,看来这确实是linux机制的问题。

setns()
pid = fork();
if(!pid) { // son
execve(bash);
}

2)pid缓存问题

编写了demo4:

int main()
{int ret, fd, pid;printf("father pid:%d\n", getpid());printf("father pid:%d\n", getpid());printf("father pid:%d\n", getpid());printf("father pid:%d\n", getpid());fd = open("/dev/ns", O_RDWR);ret = ioctl(fd, 24635); // parm is dst ns process's pidprintf("father pid old:%d\n", getpid());pid = fork();if(0 == pid) {printf("son pid:%d\n", getpid());sleep(5);} else {printf("father pid:%d\n", getpid());wait();}return 0;
}

看下结果,fork前后打印一致

# ./a.out
father pid:23246
father pid:23246
father pid:23246
father pid:23246
father pid old:23246
father pid:23246
son pid:96

strace看下,果然和我们猜的一样,除了第一次getpid调用了syscall后,后边所有的返回值均是从缓存中获取的,所以这就能解释为什么不执行getpid并fork后执行getpid获取到的返回值是0,而执行了getpid并fork后再执行getpid获取到的返回值不变。

# strace ./a.out
execve("./a.out", ["./a.out"], [/* 29 vars */]) = 0
brk(0)                                  = 0x18f4000
access("/etc/ld.so.nohwcap", F_OK)      = -1 ENOENT (No such file or directory)
mmap(NULL, 8192, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7fa04d79c000
access("/etc/ld.so.preload", R_OK)      = -1 ENOENT (No such file or directory)
open("/etc/ld.so.cache", O_RDONLY)      = 3
fstat(3, {st_mode=S_IFREG|0644, st_size=105026, ...}) = 0
mmap(NULL, 105026, PROT_READ, MAP_PRIVATE, 3, 0) = 0x7fa04d782000
close(3)                                = 0
access("/etc/ld.so.nohwcap", F_OK)      = -1 ENOENT (No such file or directory)
open("/lib/libc.so.6", O_RDONLY)        = 3
read(3, "\177ELF\2\1\1\0\0\0\0\0\0\0\0\0\3\0>\0\1\0\0\0@\356\1\0\0\0\0\0"..., 832) = 832
fstat(3, {st_mode=S_IFREG|0755, st_size=1478056, ...}) = 0
mmap(NULL, 3586120, PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_DENYWRITE, 3, 0) = 0x7fa04d215000
mprotect(0x7fa04d377000, 2097152, PROT_NONE) = 0
mmap(0x7fa04d577000, 20480, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x162000) = 0x7fa04d577000
mmap(0x7fa04d57c000, 18504, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_ANONYMOUS, -1, 0) = 0x7fa04d57c000
close(3)                                = 0
mmap(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7fa04d781000
mmap(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7fa04d780000
mmap(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7fa04d77f000
arch_prctl(ARCH_SET_FS, 0x7fa04d780700) = 0
mprotect(0x7fa04d577000, 16384, PROT_READ) = 0
mprotect(0x7fa04d79e000, 4096, PROT_READ) = 0
munmap(0x7fa04d782000, 105026)          = 0
getpid()                                = 23253
fstat(1, {st_mode=S_IFCHR|0600, st_rdev=makedev(136, 4), ...}) = 0
mmap(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7fa04d79b000
write(1, "father pid:23253\n", 17father pid:23253
)      = 17
write(1, "father pid:23253\n", 17father pid:23253
)      = 17
write(1, "father pid:23253\n", 17father pid:23253
)      = 17
write(1, "father pid:23253\n", 17father pid:23253
)      = 17
open("/dev/ns", O_RDWR)                 = 3
ioctl(3, 0x603b, 0x7fa04d57cdf0)        = 0
write(1, "father pid old:23253\n", 21father pid old:23253
)  = 21
clone(child_stack=0, flags=CLONE_CHILD_CLEARTID|CLONE_CHILD_SETTID|SIGCHLD, child_tidptr=0x7fa04d7809d0) = 97
write(1, "father pid:23253\n", 17father pid:23253
)      = 17
wait4(-1, son pid:97
0xffffffff, 0, NULL)          = -1 EFAULT (Bad address)
--- SIGCHLD (Child exited) @ 0 (0) ---

3)为什么子进程实现了pid ns的切换,父进程却没有实现?

先来看看setns的实现,在2.6.32中要支持setns很简单首先通过不创建新NS的方式调用copy_namespaces并传入dst ns,这会增加dst ns的引用,之后通过switch_task_namespaces传入current task与dst ns,在函数中会首先进行nsproxy指针的交换,将当前task切换到dst ns中,之后src ns减引用,这样就能保证引用数量的正确。因此这里实际上切换了current task的nsproxy。

copy_namespaces->switch_task_namespaces
int copy_namespaces(unsigned long flags, struct task_struct *tsk)
{struct nsproxy *old_ns = tsk->nsproxy;struct nsproxy *new_ns;int err = 0;if (!old_ns)return 0;get_nsproxy(old_ns);if (!(flags & (CLONE_NEWNS | CLONE_NEWUTS | CLONE_NEWIPC |CLONE_NEWPID | CLONE_NEWNET)))return 0;
。。。
}void switch_task_namespaces(struct task_struct *p, struct nsproxy *new)
{struct nsproxy *ns;might_sleep();ns = p->nsproxy;rcu_assign_pointer(p->nsproxy, new);if (ns && atomic_dec_and_test(&ns->count)) {/** wait for others to get what they want from this nsproxy.** cannot release this nsproxy via the call_rcu() since* put_mnt_ns() will want to sleep*/synchronize_rcu();free_nsproxy(ns);}
}

再看看getpid的调用流程(sys_getpid–>task_tgid_vnr->pid_vnr->pid_nr_ns),这里考虑线程问题通过task_tgid获取该PID的tgid,因为应用层和内核对PID的定义不同,内核中进程与线程都拥有相同的结构体描述struct task_struct,因此进程与线程在内核中均拥有自己独立的PID,因此这时候找到“用户态PID”的关键是找到根进程,因为父根进程的pid与tgid是一致如下,因此在这里传入tgid来代替父根进程的PID。在pid_nr_ns中会进行两个判断,一是该pid ns的level应大于指定pid ns的level,这里的pid ns level如下图所示是一个树状结构,这里default pid ns中的pid level为0,而基于某进程创建的CLONE_NEWPID的进程,其pid ns level + 1,因此这里的判断会出现问题,因为setns只修改了current->nsproxy,如果是在default pid ns中执行的demo程序以及lxc程序,那么current->nsproxy->pid_ns->level = 1,而pid->level = 0,这会导致判断失败直接返回0,这也是我们在demo程序中通过getpid()得到0的原因。

 <-- PID 43 --> <----------------- PID 42 ----------------->+---------+| process |_| pid=42  |__/ | tgid=42 | \_ (new thread) __ (fork) _/   +---------+                  \/                                        +---------+
+---------+                                    | process |
| process |                                    | pid=44  |
| pid=43  |                                    | tgid=42 |
| tgid=43 |                                    +---------+
+---------+<-- PID 43 --> <--------- PID 42 --------> <--- PID 44 --->

SYSCALL_DEFINE0(getpid)
{return task_tgid_vnr(current);
}static inline pid_t task_tgid_vnr(struct task_struct *tsk)
{return pid_vnr(task_tgid(tsk));
}pid_t pid_vnr(struct pid *pid)
{return pid_nr_ns(pid, current->nsproxy->pid_ns);
}pid_t pid_nr_ns(struct pid *pid, struct pid_namespace *ns)
{struct upid *upid;pid_t nr = 0;if (pid && ns->level <= pid->level) {upid = &pid->numbers[ns->level];if (upid->ns == ns)nr = upid->nr;}return nr;
}

如下,这里有对获取到的upid->nr的一个很好的解释,也就是说upid是pid ns有效的,另外这个实验是在2.6.32.5中实现的,而2.6.32.5中不支持setns(我对它进行了移植),或许在3.x的某些支持setns的版本中也能复现,但4.9.0中pid_nr_ns的不同实现导致了实验效果的不同,在4.9.0中demo程序会返回default ns中的pid。

/** struct upid is used to get the id of the struct pid, as it is* seen in particular namespace. Later the struct pid is found with* find_pid_ns() using the int nr and struct pid_namespace *ns.*/
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;
};

最后再补充下,前面可以看到setns实现其实只修改了task_struct中的nsproxy,因此fork会通过调用链do_fork->copy_process->alloc_pid(p->nsproxy->pid_ns)也就是利用父进程的nsproxy->pid_ns来创建自己的pid,这样就说明了为什么lxc中必须fork出子进程来执行execve。

setns对当前进程无效问题的排查(getpid获取值不变)相关推荐

  1. 服务器上tomcat进程突然终止问题排查 - OOM Killer

    服务器上tomcat进程突然终止问题排查 1)检查是否shell进程终止导致tomcat终止(排除) 由于使用./catalina.sh start启动tomcat,启动后tomcat的父PID为1, ...

  2. 线上服务Java进程假死快速排查、分析

    线上服务Java进程假死快速排查.分析 最近我们有一台服务器上的Java进程总是在运行个两三天后就无法响应请求了,具体现象如下: 请求业务返回状态码502,查看进程还在,意味着Java进程假死,无法响 ...

  3. 【Android 逆向】Android 进程注入工具开发 ( SO 进程注入环境及 root 权限获取 | 进程注入时序分析 )

    文章目录 一.SO 进程注入环境及 root 权限获取 二.进程注入时序分析 一.SO 进程注入环境及 root 权限获取 SO 注入的前提必须有 root 权限 , 有了 root 权限后 , 才能 ...

  4. 微信小程序背景音乐官方实例代码无效问题解决及音乐src获取方法

    微信小程序背景音乐官方实例代码无效问题解决及音乐src获取方法 参考文章: (1)微信小程序背景音乐官方实例代码无效问题解决及音乐src获取方法 (2)https://www.cnblogs.com/ ...

  5. node进程cpu 100%问题排查

    案情描述 现象:监控系统显示,官网的服务每个月总有那么几天会烧脑,单台服务器qps 20以内的前提下,从某一刻开始,node进程持续性消耗cpu100%,并且在剔除流量后,居高不下. 问题:cpu持续 ...

  6. Java进程CPU使用率高排查

    1.使用top 定位到占用CPU高的进程PID top 通过ps aux | grep PID命令 2.获取线程信息,并找到占用CPU高的线程 ps -mp pid -o THREAD,tid,tim ...

  7. Tomcat进程假死问题排查

    目录 1.网络 1.1 检查nginx的网络情况 1.2 检查tomcat的网络情况 2.Jvm内存溢出 2.1为什么会发生内存泄漏 2.2快速定位问题 2.3 jstack查看tomcat是否出现死 ...

  8. Java死锁和Java进程Java CPU 100%排查

    三板斧:top -> top -Hp ->jstack 通过 top 命令找到 CPU 消耗最多的进程号: 通过 top -Hp 进程号 命令找到 CPU 消耗最多的线程号(列名仍然为 P ...

  9. 记一次 oracle ORA-01722: 无效数字 错误排查

    先上sql select count(*) from user t where t.customerid !=4099; // 报错 无效数字 select * from user t where t ...

最新文章

  1. ServiceMesh架构的演变过程概述
  2. linux shell 字符串查找
  3. tomcat占用cpu比较多
  4. 【机器学习基础】时间序列基本概念
  5. nyoj 55 懒省事的小明 优先队列 multiset 还有暴力
  6. 05_HttpClient_模拟登陆
  7. 关于NFS服务器的原理总结和mount挂载
  8. 解决谷歌浏览器所有页面崩溃问题
  9. Nacos分布式配置实践
  10. mayapython开关_Maya Python 简易教程.doc
  11. brew源码安装mysql_mac使用brew安装mysql
  12. 【原创】CPU 100%+磁盘写满 问题排查
  13. 你所不知道的 AI 进展
  14. 菜鸟抓鸡--各个端口的***总结
  15. jet-cp4005,linux双面打印,如何使用Linux(Ubuntu 13.04)在HP 7610上启用双面打印
  16. extremecomponents
  17. pycharm如何更换背景图片
  18. SVC vs SVR
  19. Excel个人所得税简洁计算公式
  20. 【NLP】语料库和词汇知识库

热门文章

  1. Word怎么画流程图?1分钟快速学会绘制精美流程图
  2. canvas星空连线背景特效
  3. 锤子科技 Smartisan M1L 咖啡金 真皮背面 高配版 5.7
  4. Elastic 发布 Elasticsearch Relevance Engine™ — 为 AI 革命提供高级搜索能力
  5. GHOST 博客安装中文全攻略
  6. [转]安装黑苹果卡在applekeystore starting
  7. Cloudreve搭建云盘系统,并实现随时访问
  8. Arduino读取传感器数据存进Excel中
  9. 2012年终总结spring mvc-----AOP
  10. 工程经济学知识点总结