Programming with PTRACE, Part1 - 起步
前言
本人作为一个信息学竞赛的参与者,在很久之前曾经试图自己写过一个Online Judge系统(允许用户上传源代码并在服务器上编译运行),考虑到安全因素,必须要对程序的行为进行限制,因此对ptrace进行了一番研究。网上有一份关于ptrace的很好的教程(Playing with ptrace),但是时间有点久了,而且没有涉及64位操作系统。因此,我决定写这份教程,基于64位Linux,尽力介绍一些新加入的功能,同时兼顾一下32位系统。另外,由于一开始的目的是“对程序的行为进行限制”,所以不会涉及到诸如设置断点之类的内容,相反,可能会涉及到其他关于系统资源管理的内容。ptrace()是一个由Linux内核提供的系统调用。它允许一个用户态进程检查、修改另一个进程的内存和寄存器。这种技术被广泛用于gdb等调试器中。尽管这系列文章的标题叫做“Programming with PTRACE”,但在第一部分中,我将着重介绍Linux的进程和相关的几个重要函数。
fork(), vfork() 与 clone()
在Linux中,每一个进程都有一个唯一的编号,被称作pid(Process ID)。在Linux中,进程不能凭空产生(init进程是个例外),只能从一个已有进程衍生出来。原来的进程被称做父进程,衍生出来的进程叫子进程。一个系统中所有进程以父子关系相连接,形成一棵树,这棵“树”的树根就是init进程,它是在系统启动时被直接启动的,因此它没有父进程。并且系统中所有其他进程都直接或间接地是它的子进程。在Linux系统中,实现“把一个进程变成两个”这一功能的有三个系统调用,即fork()、vfork()和clone()。
fork()的工作流程的确和叉子有几分相似之处,它将当前进程所有数据复制一份,产生一个和父进程一模一样的子进程。并在两个进程中返回不同的返回值。比如这段代码:
1 |
|
将会输出
Program started.
fork() returned 5768
fork() returned 0很明显地可以看到,puts()只被调用了一次而printf()被调用了两次,这说明在fork()前的一个进程变成了两个,而且fork()在两个进程中有不同的返回值(这就是“调用一次,返回两次”的来历)。fork()会返回0给子进程,返回子进程的pid给父进程,因此,我们很容易判断出fork() returned 0是由子进程打印的。在实际应用中,也通过if语句判断返回值的方法来决定执行不同的代码:
int pid=fork();
if (pid==0){
//子进程的工作
}else{
//父进程的工作
}一般来说,子进程的工作就是调用exec族函数,启动另一个程序(把自己替换掉)。如果子进程还在执行而父进程已结束,那么它就成为“孤儿”进程,成为init进程的子进程。另外,请不要纠结那个if判断带来的性能损失,Linux的内核开发者都不纠结,你纠结什么呢?
