fork源码分析
来源:互联网 发布:微商城网站源码 编辑:程序博客网 时间:2024/04/27 22:38
1.kernel/system_call.s
208 _sys_fork:
209 call _find_empty_process
210 testl %eax,%eax
211 js 1f
212 push %gs
213 pushl %esi
214 pushl %edi
215 pushl %ebp
216 pushl %eax
217 call _copy_process
218 addl $20,%esp
219 1: ret
先看一下_find_empty_process调用:
136 {
137 int i;
138
139 repeat:
140 if ((++last_pid)<0) last_pid=1;
141 for(i=0 ; i<NR_TASKS ; i++)
142 if (task[i] && task[i]->pid == last_pid) goto repeat;
143 for(i=1 ; i<NR_TASKS ; i++)
144 if (!task[i])
145 return i;
146 return -EAGAIN;
147 }
可以看到last_pid记录了最近使用的一个pid的下一个位置,140-142行的for循环是用来维护last_pid的,143-145是找一个目前的task没有用过的最小的pid,并返回它它在task中的位置。
64 * Ok, this is the main fork-routine. It copies the system process
65 * information (task[nr]) and sets up the necessary registers. It
66 * also copies the data segment in it's entirety.
67 */
68 int copy_process(int nr,long ebp,long edi,long esi,long gs,long none,
69 long ebx,long ecx,long edx,
70 long fs,long es,long ds,
71 long eip,long cs,long eflags,long esp,long ss)
72 {
73 struct task_struct *p;
74 int i;
75 struct file *f;
76
77 p = (struct task_struct *) get_free_page();
78 if (!p)
79 return -EAGAIN;
继续
81 *p = *current; /* NOTE! this doesn't copy the supervisor stack */
82 p->state = TASK_UNINTERRUPTIBLE;
83 p->pid = last_pid;
84 p->father = current->pid;
85 p->counter = p->priority;
86 p->signal = 0;
87 p->alarm = 0;
88 p->leader = 0; /* process leadership doesn't inherit */
89 p->utime = p->stime = 0;
90 p->cutime = p->cstime = 0;
91 p->start_time = jiffies;
92 p->tss.back_link = 0;
93 p->tss.esp0 = PAGE_SIZE + (long) p;
94 p->tss.ss0 = 0x10;
95 p->tss.eip = eip;
96 p->tss.eflags = eflags;
97 p->tss.eax = 0; //所以子进程返回0?
98 p->tss.ecx = ecx;
99 p->tss.edx = edx;
101 p->tss.esp = esp;
102 p->tss.ebp = ebp;
103 p->tss.esi = esi;
104 p->tss.edi = edi;
105 p->tss.es = es & 0xffff;
106 p->tss.cs = cs & 0xffff;
107 p->tss.ss = ss & 0xffff;
108 p->tss.ds = ds & 0xffff;
109 p->tss.fs = fs & 0xffff;
110 p->tss.gs = gs & 0xffff;
111 p->tss.ldt = _LDT(nr);
include/linux/sched.h:154:#define FIRST_LDT_ENTRY (FIRST_TSS_ENTRY+1)
include/linux/sched.h:153:#define FIRST_TSS_ENTRY 4
112 p->tss.trace_bitmap = 0x80000000;
114 __asm__("clts ; fnsave %0"::"m" (p->tss.i387));
115 if (copy_mem(nr,p)) {
116 task[nr] = NULL;
117 free_page((long) p);
118 return -EAGAIN;
119 }
我们看一下copy_mem的实现:
39 int copy_mem(int nr,struct task_struct * p)
40 {
41 unsigned long old_data_base,new_data_base,data_limit;
42 unsigned long old_code_base,new_code_base,code_limit;
43
44 code_limit=get_limit(0x0f);
45 data_limit=get_limit(0x17);
46 old_code_base = get_base(current->ldt[1]);
47 old_data_base = get_base(current->ldt[2]);
48 if (old_data_base != old_code_base)
49 panic("We don't support separate I&D");
50 if (data_limit < code_limit)
51 panic("Bad data_limit");
52 new_data_base = new_code_base = nr * 0x4000000;
53 p->start_code = new_code_base;
54 set_base(p->ldt[1],new_code_base);
55 set_base(p->ldt[2],new_data_base);
189 __asm__("movw %%dx,%0\n\t" \
190 "rorl $16,%%edx\n\t" \
191 "movb %%dl,%1\n\t" \
192 "movb %%dh,%2" \
193 ::"m" (*((addr)+2)), \
194 "m" (*((addr)+4)), \
195 "m" (*((addr)+7)), \
196 "d" (base) \
197 :"dx")
可以看到这里就是把基址设置到了ldt中。
56 if (copy_page_tables(old_data_base,new_data_base,data_limit)) {
57 free_page_tables(new_data_base,data_limit);
58 return -ENOMEM;
59 }
可以看到这里是拷贝页表的操作,我们来看一下具体操作:
150 int copy_page_tables(unsigned long from,unsigned long to,long size)
151 {
152 unsigned long * from_page_table;
153 unsigned long * to_page_table;
154 unsigned long this_page;
155 unsigned long * from_dir, * to_dir;
156 unsigned long nr;
157
158 if ((from&0x3fffff) || (to&0x3fffff))
159 panic("copy_page_tables called with wrong alignment");
160 from_dir = (unsigned long *) ((from>>20) & 0xffc); /* _pg_dir = 0 */
获取原进程的页目录项
161 to_dir = (unsigned long *) ((to>>20) & 0xffc);
获取目标进程的页目录项
162 size = ((unsigned) (size+0x3fffff)) >> 22;
163 for( ; size-->0 ; from_dir++,to_dir++) {
164 if (1 & *to_dir)
165 panic("copy_page_tables: already exist");
166 if (!(1 & *from_dir))
167 continue;
168 from_page_table = (unsigned long *) (0xfffff000 & *from_dir);
从页目录中取得源进程的页表地址。
169 if (!(to_page_table = (unsigned long *) get_free_page()))
170 return -1; /* Out of memory, see freeing */
171 *to_dir = ((unsigned long) to_page_table) | 7;
172 nr = (from==0)?0xA0:1024;
173 for ( ; nr-- > 0 ; from_page_table++,to_page_table++) {
174 this_page = *from_page_table;
175 if (!(1 & this_page))
176 continue;
177 this_page &= ~2;
178 *to_page_table = this_page;
这里需要注意了,copy_on_write的实现与这里有重大关系。177行把页表项的R/W位清零,也就是设置只读模式。178行把它设置给目标进程的的页表中。那么当子进程被调度后运行过程中如果进行写操作,就会触发写保护异常,相应的处理函数就会为它分配新的内存页。
179 if (this_page > LOW_MEM) {
180 *from_page_table = this_page;
181 this_page -= LOW_MEM;
182 this_page >>= 12;
183 mem_map[this_page]++;
184 }
185 }
186 }
187 invalidate(); //刷新缓存
188 return 0;
189 }
60 return 0;
61 }
============
121 if (f=p->filp[i])
122 f->f_count++;
123 if (current->pwd)
124 current->pwd->i_count++;
125 if (current->root)
126 current->root->i_count++;
127 if (current->executable)
128 current->executable->i_count++;
129 set_tss_desc(gdt+(nr<<1)+FIRST_TSS_ENTRY,&(p->tss));
130 set_ldt_desc(gdt+(nr<<1)+FIRST_LDT_ENTRY,&(p->ldt));
131 p->state = TASK_RUNNING; /* do this last, just in case */
132 return last_pid;
133 }
66 #define set_ldt_desc(n,addr) _set_tssldt_desc(((char *) (n)),addr,"0x82")
53 __asm__ ("movw $104,%1\n\t" \
54 "movw %%ax,%2\n\t" \
55 "rorl $16,%%eax\n\t" \
56 "movb %%al,%3\n\t" \
57 "movb $" type ",%4\n\t" \
58 "movb $0x00,%5\n\t" \
59 "movb %%ah,%6\n\t" \
60 "rorl $16,%%eax" \
61 ::"a" (addr), "m" (*(n)), "m" (*(n+2)), "m" (*(n+4)), \
62 "m" (*(n+5)), "m" (*(n+6)), "m" (*(n+7)) \
63 )
看懂这些还需要一些补充
154 #define FIRST_LDT_ENTRY (FIRST_TSS_ENTRY+1)
155 #define _TSS(n) ((((unsigned long) n)<<4)+(FIRST_TSS_ENTRY<<3))
156 #define _LDT(n) ((((unsigned long) n)<<4)+(FIRST_LDT_ENTRY<<3))
157 #define ltr(n) __asm__("ltr %%ax"::"a" (_TSS(n)))
158 #define lldt(n) __asm__("lldt %%ax"::"a" (_LDT(n)))
可以看到,129, 130 两行主要就是把一些信息设置到gdt的相应位置。
65行语句对应如下:
0
1000 1001
Base
Base p->tss
$104
0
1000 0010
Base
p->ldt
$104
由于创建新进程时是完全通过复制父进程的代码段和数据段的方式来完成的。因此在创建新进程时,为了确保子进程的用户态堆栈没有父进程的多余信息,要求父进程在创建子进程时不要调用函数。因此fork()不能以函数形式被调用。V0.11中通过定义内嵌宏代码,调用linux的系统调用中断0x80首先fork()调用。
宏定义如下:
#define _syscall0(type,name) \
type name(void) \
{ \
long __res; \
__asm__ volatile ( "int $0x80" \ // 调用系统中断0x80。
:"=a"
(__res) \ // 返回值??eax(__res)。
:""
(__NR_
##name)); \ // 输入为系统中断调用号__NR_name。
if (__res >= 0) \ // 如果返回值>=0,则直接返回该值。
return (type) __res; errno = -__res; \ // 否则置出错号,并返回-1。
return -1;
}
static inline _syscall0(int, fork)
当调用fork创建子进程时上面的内联宏被调用,系统的中断调用号为_NR_fork被存入eax中,同时调用系统调用中断0x80,将用户态的ss, esp, eflags, cs, eip压栈
进程堆栈转入内核栈:
0x80调用system_call:
_system_call:
cmpl $nr_system_calls-1,eax #调用号若超出范围就在eax中置-1并退出
ja bad_sys_call
push ds保存原寄存器的值
push es
push fs
pushl edx #若有参数,则edx,ecx,ebx中装有函数的参数
pushl ecx
pushl ebx
movl $0x10,edx
mov dx,ds将ds,es指向内核数据段(全局描述附表GDT中数据段描述符)
mov dx,es
movl $0x17,edx 将fs指向局部数据段描述符(局部描述表LDT中数据段描述符)
mov dx,fs
call _sys_call_table(,eax,4)调用系统调用处理函数数组中的第eax个函数
pushl eax
调用call _sys_call_table(_NR_fork,4)内核栈状态:
_sys_call_table[eax*4]指向函数_sys_fork
_sys_fork:
call _find_empty_process #获得新进程的进程号pid=last_pid,返回进程在进程数组task[]中的任务号nr存入eax,nr最大为63,因为linux 0.11最多同时支持64个进程同时运行,
testl eax,eax
js 1f
push gs
pushl esi
pushl edi
pushl ebp
pushl eax
call _copy_process
addl $20,esp
ret
相信此时调用_copy_process时内核堆栈的使用情况你应该了解了,这里我就不在画出来了。
_copy_process实现功能
- 在内存中为新进程申请一页内存作为新进程的内核堆栈,并将新进程的任务数据结构存入栈底部。
- 对从父进程复制来的任务数据结构做一定的修改。
- 在线性地址空间中设置新进程的基地址和限长,并将父进程的数据段和代码段(其实是同一段)复制到子 进程的数据段和代码段。
- 如果父进程打开某文件,则将次文件的引用次数加一,将父进程的pwd,root, executable引用次数加一,因为子进程也引用了这些i节点。
- 在全局段描述符表GDT中加入子进程的TSS,LDT段描述符,返回子进程的pid,回到_sys_fork
addl $20,esp 将_copy_process压栈的所有内容全部退栈。此时eax中存的是子进程的pid
从_sys_fork返回到_system_call,将子进程号压栈
这里我们不在考虑进程处于非就绪态和时间片用完的情况。
将内核栈中内容依次pop出栈存到相应寄存器中,再调用iret使得堆栈指针重新指到进程用户态堆栈,回到进程用户态继续执行程序,子进程会在进程调度函数schedule()执行时被调用
2.总结
现在我们来总结一下fork的整个处理流程。从C语言中的函数开始,它在glibc库中会被转换为int0x80加调用号的形式,触发中断。该中断在系统初始化过程中注册,它的处理函数是system_call,这个函数在system_call.s文件中,在这里面它首先压栈一些参数,然后会根据调用号调用sys_call_table的相应表项,sys_call_table定义在include/linux/sys.h中,它是一个函数指针数组。现在对应的就是sys_fork函数,它仍然是在system_call.s中定义的。我们来看它的处理过程,首先它会调用find_empty_process函数来从task数组中查找一个还没有使用的task,
- linux fork源码分析
- fork源码分析
- fork源码概要分析
- UNIX v6 fork()源码分析
- Linux内核 fork 源码分析
- fork()函数的do_fork()源码分析
- fork()源码
- java.util.concurrent 包源码分析之Fork/Join框架
- fork用法源码示例
- fork之源码剖析
- fork源码简析
- linux源码解析-fork
- fork源码剖析
- fork函数分析
- fork实例分析
- 进程fork分析;l
- fork原理分析
- 系统调用分析:fork
- iOS-navigation中左滑pop的三种方法
- Git 10 周年访谈:Linus 讲述背后故事
- iPhone分辨率及尺寸
- java读取文件
- 3DS 文件格式
- fork源码分析
- 坚持一件事情也是挺难的,重新振作
- Bootstrap Rest Webservice
- 基于Mysql C Api编写mysql客户端程序 多线程
- 二叉树中序遍历非递归
- console使用 Chrome控制台
- Java 编程中关于异常处理的 10 个最佳实践
- 蒋鑫:为什么 Git 比 SVN 好
- 转行了