fork函数分析

首先编写fork.c和fork-asm.c来了解fork系统调用的运行结果:

fork.c代码

int main()
{
    pid_t fpid;
    int count = 0;
    fpid = fork();
    if(fpid < 0)
            printf("error in fork!\n");
    else if(fpid == 0)
    {
            printf("it's a child process,my process id is %d\n",getpid());
            count++;
    }
    else
    {
            printf("it's a parent process,my process id is %d\n",getpid());
            count ++;
    }
    printf("count:%d\n",count);
    return 0;
}

fork-asm.c代码

int main()
{
    pid_t fpid;
    int count = 0;
    asm volatile(
            "mov $0x02,%%eax\n\t"//fork对应系统调用表的第二项
            "int $0x80\n\t" //这里是模拟实际的系统调用,进入sys_call函数
            "mov %%eax,%0\n\t"
            :"=m"(fpid)
    );
    if(fpid < 0)
            printf("error in fork!\n");
    else if(fpid == 0)
    {
            printf("it's a child process,my process id is %d\n",getpid());
            count++;
    }
    else
    {
            printf("it's a parent process,my process id is %d\n",getpid());
            count ++;
    }
    printf("count:%d\n",count);
    return 0;
}

运行结果如下:

运行流程如下:

fork函数通过系统调用创建一个与原来进程几乎完全相同的进程,一个进程调用fork函数后,系统先给新的进程分配资源,例如存储数据和代码的空间。

  • 在父进程中,fork返回新创建子进程的进程ID
  • 在子进程中,fork返回0
  • 如果出现错误,fork返回一个负值

其实就相当于链表,进程形成了链表,父进程的fpid(p 意味point)指向子进程的进程id, 因为子进程没有子进程,所以其fpid为0.

fork系统调用过程

  上面对我们对fork的执行过程进行解释一下,来看它究竟做了哪些操作。 当你调用fork函数时,linux底层究竟怎样进行怎样的操作?为此,我查看linux内核0.11版本的源码来理解。

代码路径:init/main.c
static inline _syscall0(int,fork)

内核通过内联操作,在调用fork函数时,实际上是执行到unistd.h中的宏函数syscall0中去。对应代码:

#define __NR_setup	0	/* used only by init, to get system going */
#define __NR_exit	1
#define __NR_fork	2
#define __NR_read	3
#define __NR_write	4
#define __NR_open	5
		………
#define _syscall0(type,name) \
type name(void) \
{ \
long __res; \
__asm__ volatile ("int $0x80" \
	: "=a" (__res) \
	: "0" (__NR_##name)); \
if (__res >= 0) \
	return (type) __res; \
errno = -__res; \
return -1; \
}

  首先进入_syscall0后,先执行:”0”(__NR_fork)是将fork在sys_call_table[]中对应的函数编号__NR_fork(也就是2)赋值给eax,(在sys_call_table[]中编号2即对应sys_fork函数)。然后执行int $0x80软中断,在set_system_gate(0x80,&system_call);(/linux/kernel/Sched.c中的sched_init函数里)中定义了中断0x80对应着system_call函数(再由iret翻转回到进程的3特权级),所以就会跳转到_system_call中继续执行。

fork系统调用执行流程:

sys_fork里面会做的事:
(1)find_empty_process():在task[64]中为进程申请一个空闲位置并获取进程号
(2)copy_process():

  • 为新进程创建task_struct,将原先进程的task_struct的内容复制给新进程
  • 给新进程分配页表,并复制原先进程的页表到新进程
  • 共享原先进程的文件
  • 设置新进程的GDT项
  • 将新进程设置成就绪态,参与进程间的轮转

0x80中断

系统调用是要在0特权级下的完成的,也是为了其安全性,那么3特权级翻转到0特权级时发生了什么?

代码路径:\linux\include\asm\System.h
#define set_system_gate(n,addr) \
	_set_gate(&idt[n],15,3,addr)
		……
#define _set_gate(gate_addr,type,dpl,addr) \
__asm__ ("movw %%dx,%%ax\n\t" \
	"movw %0,%%dx\n\t" \
	"movl %%eax,%1\n\t" \ //这里在拼接IDT表
	"movl %%edx,%2" \
	: \
	: "i" ((short) (0x8000+(dpl<<13)+(type<<8))), \
	"o" (*((char *) (gate_addr))), \
	"o" (*(4+(char *) (gate_addr))), \
	"d" ((char *) (addr)),"a" (0x00080000))  

dpl为3表示系统调用可以由3特权级调用,所以set_system_gate可以在用户态调用,中断使CPU硬件自动将SS、ESP、EFLAGS、CS、EIP寄存器的数值压入内核栈,这里发生了内核态和用户态堆栈的切换,这两个操作都在system_call的一开始进行了,从system_call开始就是在内核态了。