linux0.12之内核代码之『深入追踪fork函数』

原创 2015年07月09日 08:55:36

在上一篇fork.c分析中简单分析了内核中fork的实现,那从用户层去分析fork函数的实现。
目前已经知道这是一个系统调用函数,看看能不能找到fork函数原型,很不幸花了十分又十分钟,还是没有找到。
但是在内核init中的main.c有调用fork函数,就以此为线索吧。

void main(void)  /* This really IS void, no error here. */
{    /* The startup routine assumes (well, ...) this */
/*
 * Interrupts are still disabled. Do necessary setups, then
 * enable them
 */
  ROOT_DEV = ORIG_ROOT_DEV;
  SWAP_DEV = ORIG_SWAP_DEV;
 sprintf(term, "TERM=con%dx%d", CON_COLS, CON_ROWS);
 envp[1] = term;    
 envp_rc[1] = term;
  drive_info = DRIVE_INFO;
 memory_end = (1<<20) + (EXT_MEM_K<<10);
 memory_end &= 0xfffff000;
 if (memory_end > 16*1024*1024)
  memory_end = 16*1024*1024;
 if (memory_end > 12*1024*1024) 
  buffer_memory_end = 4*1024*1024;
 else if (memory_end > 6*1024*1024)
  buffer_memory_end = 2*1024*1024;
 else
  buffer_memory_end = 1*1024*1024;
 main_memory_start = buffer_memory_end;
#ifdef RAMDISK
 main_memory_start += rd_init(main_memory_start, RAMDISK*1024);
#endif
 mem_init(main_memory_start,memory_end);
 trap_init();
 blk_dev_init();
 chr_dev_init();
 tty_init();
 time_init();
 sched_init();
 buffer_init(buffer_memory_end);
 hd_init();
 floppy_init();
 sti();
 move_to_user_mode();
 if (!fork()) {  /* we count on this going ok */
  init();
 }

最后有提到fork,但是利用怎么也找不到fork这个函数的定义,只在unistd.h中有声明。
那就搜索含有fork的关键字吧,
在main.c的最上面有

/*
 * we need this inline - forking from kernel space will result
 * in NO COPY ON WRITE (!!!), until an execve is executed. This
 * is no problem, but for the stack. This is handled by not letting
 * main() use the stack at all after fork(). Thus, no function
 * calls - which means inline code for fork too, as otherwise we
 * would use the stack upon exit from 'fork()'.
 *
 * Actually only pause and fork are needed inline, so that there
 * won't be any messing with the stack from main(), but we define
 * some others too.
 */
static inline _syscall0(int,fork)
static inline _syscall0(int,pause)
static inline _syscall1(int,setup,void *,BIOS)
static inline _syscall0(int,sync)

出现fork函数了这是一个宏调用,这是一个内联函数,

#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; \
}

将这个宏展开,就会得到fork的函数实现
来来展开看看

syscall0(int,fork)

int fork(void)
{
 long __res;    \
 __asm__ volatile ( "int  $0x80" \
      :"=a" (__res) \
      :"0"  (__NR_fork) \
      ); \
 if(__res >= 0)
  return (int) __res;
 errno = -__res;
 return -1;
}

int 0x80就是系统调用,将NR_fork调用号注册进去

#define __NR_fork   2

看看 int 0x80 会中断执行什么,
首先我们知道,linux0.12中会有一个中断描述符表(IDT),就类似于中断向量表,
那常规理解,肯定要去注册才能使用,
所以在sched.c调度代码中有一个初始化调度函数

void sched_init(void)
{
 int i;
 struct desc_struct * p;

 if (sizeof(struct sigaction) != 16)
  panic("Struct sigaction MUST be 16 bytes");
 set_tss_desc(gdt+FIRST_TSS_ENTRY,&(init_task.task.tss));
 set_ldt_desc(gdt+FIRST_LDT_ENTRY,&(init_task.task.ldt));
 p = gdt+2+FIRST_TSS_ENTRY;
 for(i=1;i<NR_TASKS;i++) {
  task[i] = NULL;
  p->a=p->b=0;
  p++;
  p->a=p->b=0;
  p++;
 }
/* Clear NT, so that we won't have troubles with that later on */
 __asm__("pushfl ; andl $0xffffbfff,(%esp) ; popfl");
 ltr(0);
 lldt(0);
 outb_p(0x36,0x43);  /* binary, mode 3, LSB/MSB, ch 0 */
 outb_p(LATCH & 0xff , 0x40);   /* LSB */
 outb(LATCH >> 8 , 0x40);   /* MSB */
 set_intr_gate(0x20,&timer_interrupt);
 outb(inb_p(0x21)&~0x01,0x21);
 set_system_gate(0x80,&system_call);
}

其中set_system_gate(0x80,&system_call);,很吊呀,
很像呀,
看看这个函数是什么gui,在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" \
 "movl %%edx,%2" \
 : \
 : "i" ((short) (0x8000+(dpl<<13)+(type<<8))), \
 "o" (*((char *) (gate_addr))), \
 "o" (*(4+(char *) (gate_addr))), \
 "d" ((char *) (addr)),"a" (0x00080000))

大致就是将addr地址注册到idt第0x80表项中。

所以到现在 明白了一点
执行int 0x80系统就会去调用 system_call函数。(其实这就是常规的中断调用机制)

接下来回到 fork函数
在调用int 0x80时 ,还传递了一个参数放到eax中,
看看system_call是什麽gui,在sys_call.中

_system_call:
 push %ds
 push %es
 push %fs
 pushl %eax                       # save the orig_eax
 pushl %edx  
 pushl %ecx                       # push %ebx,%ecx,%edx as parameters
 pushl %ebx                       # to the system call
 movl $0x10,%edx             # set up ds,es to kernel space
 mov %dx,%ds
 mov %dx,%es
 movl $0x17,%edx             # fs points to local data space
 mov %dx,%fs
 cmpl _NR_syscalls,%eax
 jae bad_sys_call
 call _sys_call_table(,%eax,4)
 pushl %eax
其中这几句是关键
 cmpl _NR_syscalls,%eax
 jae bad_sys_call
 call _sys_call_table(,%eax,4)
在include/linux/sys.h中的
/* So we don't have to do any more manual updating.... */
int NR_syscalls = sizeof(sys_call_table)/sizeof(fn_ptr);

fn_ptr sys_call_table[] = { sys_setup, sys_exit, sys_fork, sys_read,
sys_write, sys_open, sys_close, sys_waitpid, sys_creat, sys_link,
sys_unlink, sys_execve, sys_chdir, sys_time, sys_mknod, sys_chmod,
sys_chown, sys_break, sys_stat, sys_lseek, sys_getpid, sys_mount,
sys_umount, sys_setuid, sys_getuid, sys_stime, sys_ptrace, sys_alarm,
sys_fstat, sys_pause, sys_utime, sys_stty, sys_gtty, sys_access,
sys_nice, sys_ftime, sys_sync, sys_kill, sys_rename, sys_mkdir,
sys_rmdir, sys_dup, sys_pipe, sys_times, sys_prof, sys_brk, sys_setgid,
sys_getgid, sys_signal, sys_geteuid, sys_getegid, sys_acct, sys_phys,
sys_lock, sys_ioctl, sys_fcntl, sys_mpx, sys_setpgid, sys_ulimit,
sys_uname, sys_umask, sys_chroot, sys_ustat, sys_dup2, sys_getppid,
sys_getpgrp, sys_setsid, sys_sigaction, sys_sgetmask, sys_ssetmask,
sys_setreuid,sys_setregid, sys_sigsuspend, sys_sigpending, sys_sethostname,
sys_setrlimit, sys_getrlimit, sys_getrusage, sys_gettimeofday, 
sys_settimeofday, sys_getgroups, sys_setgroups, sys_select, sys_symlink,
sys_lstat, sys_readlink, sys_uselib };
在sched.h中
typedef int (*fn_ptr)();

从这三个片段代码知道,sys_call_table就是一张函数指针数组,NR_syscalls就是用于计算数组元素的个数。
在system_call中怎么call一个数组呢,其实 call _sys_call_table(,%eax,4)
是这样的调用地址=sys_call_table + %eax*4;乘以四是因为,每个数组元素占4个字节(函数地址么)。
所以

cmpl _NR_syscalls,%eax
 jae bad_sys_call
 call _sys_call_table(,%eax,4)

的功能就是,判断调用参数是否超出系统调用的最大调用号,如果正常,调用传入调用号对应的系统函数。
整体流程为
这里写图片描述
下面的任务就是 研究sys_fork这个系统函数了.还在sys_call.s中

.align 2
_sys_fork:
 call _find_empty_process
 testl %eax,%eax
 js 1f
 push %gs
 pushl %esi
 pushl %edi
 pushl %ebp
 pushl %eax
 call _copy_process
 addl $20,%esp
1:  ret 

这里调用了两个函数 这在我上一篇已经分析了
http://blog.csdn.net/u010442328/article/details/46773737

版权声明:本文为博主原创文章,未经博主允许不得转载。

相关文章推荐

linux0.12之内核代码之fork.c说明

在system_call.s中.align 2 _sys_fork: call _find_empty_process testl %eax,%eax js 1f push %gs push...

Linux 0.12 任务长度由640KB到64M的fork转变

Linux内核的研究,很有意思和成就感 每天都能遇到问题 为了找到答案 翻阅庞大的内核代码 无解 百度 无解 再回庞大的源代码中去寻找 即使是一个小问题甚至是一行代码 都会牵扯到系统大部分的运行机制 ...

Linux内核 do_fork 函数源代码浅析

在 Linux 内核中,供用户创建进程的系统调用fork()函数的响应函数是 sys_fork()、sys_clone()、sys_vfork()。这三个函数都是通过调用内核函数 do_fork() ...
  • MyArrow
  • MyArrow
  • 2013年05月30日 16:16
  • 2681

Linux 内核--fork()函数创建进程

Linux 内核--fork()函数创建进程 分类: Linux内核游记 2011-06-05 22:24 464人阅读 评论(0) 收藏 举报 本文分析基于Linux 0....

Linux 内核--fork()函数创建进程

本文分析基于Linux 0.11内核,转载请表明出处http://blog.csdn.net/yming0221/archive/2011/06/05/6527337.aspx  Linux在move...

Linux内核0.12——8086中断

中断:CPU不再继续依序执行指令,而是转去处理某一从CPU外部或内部产生的特殊信息 从汇编角度理解: 内中断:对于8086CPU来说,以下发生在CPU内部的的情况会产生内中断: 除法错误;单步执...

linux内核完全剖析0.12笔记--第四章 80x86保护模式及其编程

这一章涉及intel8086系列cpu的保护模式编程,应该是学习内核编程,驱动编程及嵌入式编程一些基础知识。不过对于没接触过底层编程的我来说,感觉还是好复杂。   不过里面也有许多以前汇编学过的东西...

Linux 0.12内核学习之(1)——用MASM编写Boot Sector引导扇区

最近在学习Linux0.12内核,正在读《Linux内核完全剖析》。一开始就被ax86写的引导扇区弄晕了。于是Google了很多资料。最终实验了一晚上终于搞定。下面来看看我们怎么用Windows下的M...

linux内核完全剖析0.12笔记--第一章,概述

开始学习linux内核了,对linux系统的理解还不够深,对于比较新的内核理解困难,于是选择了这本讲解早期内核的书来看,并做做笔记。    第一章,概述     介绍了linux的历史,开发背景,...

Linux 0.12 内核对内存的管理

其着重点在于分段,用分段的机制把进程间的虚拟地址分隔开。 每个进程都有一张段表LDT,整个系统有一张GDT表,且整个系统只有一个总页表。   其地址翻译过程为: ...
内容举报
返回顶部
收藏助手
不良信息举报
您举报文章:linux0.12之内核代码之『深入追踪fork函数』
举报原因:
原因补充:

(最多只允许输入30个字)