UNIX环境高级编程学习笔记(十)为何 fork 函数会有两个不同的返回值【转】

本文涉及的产品
网络型负载均衡 NLB,每月750个小时 15LCU
公网NAT网关,每月750个小时 15CU
应用型负载均衡 ALB,每月750个小时 15LCU
简介:

转自:http://blog.csdn.net/fool_duck/article/details/46917377

以下是基于 linux 0.11 内核的说明。

在init/main.c第138行, 
在move_to_user_mode()之后,进程0通过fork()产生子进程,实际就是进程1(init进程)。

在main.c第23行:

static inline _syscall0(int,fork)

 

通过 _syscall0 调用 fork 。_syscall0 即不带参数的系统调用:type name(void),_syscall0 的定义在unistd.h中第133行:

#define _syscall0(type,name) \
type name(void) \ { \ long __res; \ __asm__ volatile ("int $0x80" \ //调用0x80系统中断 : "=a" (__res) \ : "0" (__NR_##name)); \ if (__res >= 0) \ return (type) __res; \ errno = -__res; \ return -1; \ }

 

在 kernel\sched.c 中的 sched_init 调用 system_call


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); } 

 

system_call 位于 kernel\system_call.s 中。在该文件第94行:

call _sys_call_table(,%eax,4)
  • 1

调用地址为:_sys_call_table + %eax * 4,此时 exa 的值为2(根据__NR_fork的定义),由于是32位机,指针占4个字节。

sys_call_table 的定义在 include\linux\sys.h 中第74行:

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_fork 。

sys_fork 的定义是一段汇编代码,位于 kernel\system_call.s 第208行:

_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 

 

在 sys_fork 中,有两个主要的函数调用:_find_empty_process 和 _copy_process 。

_find_empty_process 位于 kernel\fork.c 中第135行,其作用是找到一个空的进程号(对应于一个进程控制块PCB),在linux 0.11版本中最多支持64个进程(全局数组task定义在sched.h中,数组大小为64):

int find_empty_process(void)
{
    int i;

    repeat:
        if ((++last_pid)<0) last_pid=1; for(i=0 ; i<NR_TASKS ; i++) if (task[i] && task[i]->pid == last_pid) goto repeat; for(i=1 ; i<NR_TASKS ; i++) if (!task[i]) return i; return -EAGAIN; } 

 

全局变量last_pid用来记录上次使用的进程号,其定义在 kernel\fork.c 第22行:

    long last_pid=0;
  • 1

在find_empty_process中,不断递增last_pid,寻找第一个未被其它进程使用的进程号作为新进程的进程号。如果递增后的值超出正数表示范围,则重新从1开始,并将其返回值存放在 %eax 中。若没能找到可用进程号,则跳转。若找到可用进程号则进行相关压栈操作,然后调用_copy_process 开始复制进程内容。

_copy_process 的定义位于 kernel\fork.c 第63行:

/*
 *  Ok, this is the main fork-routine. It copies the system process
 * information (task[nr]) and sets up the necessary registers. It
 * also copies the data segment in it's entirety. */ int copy_process(int nr,long ebp,long edi,long esi,long gs,long none, long ebx,long ecx,long edx, long fs,long es,long ds, long eip,long cs,long eflags,long esp,long ss) { struct task_struct *p; int i; struct file *f; p = (struct task_struct *) get_free_page(); //获为新任务分配内存 if (!p) return -EAGAIN; task[nr] = p; //将新任务结构指针放入任务数组中,其中nr 是由前面find_empty_process()返回的任务号 *p = *current; /* NOTE! this doesn't copy the supervisor stack */ p->state = TASK_UNINTERRUPTIBLE; // 将新进程的状态先置为不可中断等待状态 p->pid = last_pid; // fork 对父进程返回子进程ID p->father = current->pid; p->counter = p->priority; p->signal = 0; p->alarm = 0; p->leader = 0; /* process leadership doesn't inherit */ p->utime = p->stime = 0; p->cutime = p->cstime = 0; p->start_time = jiffies; p->tss.back_link = 0; p->tss.esp0 = PAGE_SIZE + (long) p; p->tss.ss0 = 0x10; p->tss.eip = eip; p->tss.eflags = eflags; p->tss.eax = 0; // fork 对子进程返回0 p->tss.ecx = ecx; p->tss.edx = edx; p->tss.ebx = ebx; p->tss.esp = esp; p->tss.ebp = ebp; p->tss.esi = esi; p->tss.edi = edi; p->tss.es = es & 0xffff; p->tss.cs = cs & 0xffff; p->tss.ss = ss & 0xffff; p->tss.ds = ds & 0xffff; p->tss.fs = fs & 0xffff; p->tss.gs = gs & 0xffff; p->tss.ldt = _LDT(nr); p->tss.trace_bitmap = 0x80000000; if (last_task_used_math == current) __asm__("clts ; fnsave %0"::"m" (p->tss.i387)); if (copy_mem(nr,p)) { task[nr] = NULL; free_page((long) p); return -EAGAIN; } for (i=0; i<NR_OPEN;i++) if (f=p->filp[i]) f->f_count++; if (current->pwd) current->pwd->i_count++; if (current->root) current->root->i_count++; if (current->executable) current->executable->i_count++; set_tss_desc(gdt+(nr<<1)+FIRST_TSS_ENTRY,&(p->tss)); set_ldt_desc(gdt+(nr<<1)+FIRST_LDT_ENTRY,&(p->ldt)); p->state = TASK_RUNNING; /* do this last, just in case */ return last_pid; }

 

进程控制块中还保存有进程的任务状态段数据结构tss,用于存储处理器管理进程的所有信息。也就是说,在任务切换过程中,首先将处理器中各寄存器的当前值被自动保存当前进程的tss中;然后,下一进程的tss被加载并从中提取出各个值送到处理器的寄存器中。由此可见,通过在tss中保存任务现场各寄存器状态的完整映象,实现任务的切换。

    struct tss_struct tss;

因此,一旦在task[]数组中找到空闲项和进程号,我们就可以为该进程的进程控制块结构申请一个页面的内存。这个工作是在copy_process() 函数中完成的。

当然copy_process()函数的最主要的任务是为子进程复制父进程信息,并设置子进程的任务状态段,其中最关键的两步是:

  • 把子进程tss中的eip设置为父进程系统调用返回地址,这样当子进程被调度程序选中后,将从父进程的fork()返回处开始执行。
    p->tss.eip = eip;

 

  • 把子进程tss中的eax设置为0,而eax是存放函数返回值的地方,这样子进程中返回的是0。注意子进程并没有执行fork()函数,子进程的系统堆栈没有进行过操作,当然不会有像父进程那样的fork函数调用。但是当子进程开始运行时,就好像它从 fork 中返回一样。
 

p->tss.eax = 0;








本文转自张昺华-sky博客园博客,原文链接:http://www.cnblogs.com/sky-heaven/p/8080501.html,如需转载请自行联系原作者


相关实践学习
每个IT人都想学的“Web应用上云经典架构”实战
本实验从Web应用上云这个最基本的、最普遍的需求出发,帮助IT从业者们通过“阿里云Web应用上云解决方案”,了解一个企业级Web应用上云的常见架构,了解如何构建一个高可用、可扩展的企业级应用架构。
高可用应用架构
欢迎来到“高可用应用架构”课程,本课程是“弹性计算Clouder系列认证“中的阶段四课程。本课程重点向您阐述了云服务器ECS的高可用部署方案,包含了弹性公网IP和负载均衡的概念及操作,通过本课程的学习您将了解在平时工作中,如何利用负载均衡和多台云服务器组建高可用应用架构,并通过弹性公网IP的方式对外提供稳定的互联网接入,使得您的网站更加稳定的同时可以接受更多人访问,掌握在阿里云上构建企业级大流量网站场景的方法。 学习完本课程后,您将能够: 理解高可用架构的含义并掌握基本实现方法 理解弹性公网IP的概念、功能以及应用场景 理解负载均衡的概念、功能以及应用场景 掌握网站高并发时如何处理的基本思路 完成多台Web服务器的负载均衡,从而实现高可用、高并发流量架构
相关文章
|
29天前
|
算法 Unix 数据安全/隐私保护
Python编程--UNIX口令破解机
Python编程--UNIX口令破解机
25 1
|
5月前
|
Unix
Unix环境高级编程(第三版)中apue.h头文件及其依赖安装教程
Unix环境高级编程(第三版)中apue.h头文件及其依赖安装教程
|
12月前
|
Unix Shell Python
unix高级编程-fork和execve
unix高级编程-fork和execve
55 0
|
12月前
|
Ubuntu Unix Shell
unix高级编程-fork之后父子进程共享文件
unix高级编程-fork之后父子进程共享文件
59 0
|
6月前
|
缓存 网络协议 Unix
Linux(UNIX)五种网络I/O模型与IO多路复用
Linux(UNIX)五种网络I/O模型与IO多路复用
169 0
|
6月前
|
Unix Shell Linux
在Unix/Linux操作系统中,Shell脚本广泛用于自动化任务
在Unix/Linux操作系统中,Shell脚本广泛用于自动化任务
69 2
|
22天前
|
Unix 物联网 大数据
操作系统的演化与比较:从Unix到Linux
本文将探讨操作系统的历史发展,重点关注Unix和Linux两个主要的操作系统分支。通过分析它们的起源、设计哲学、技术特点以及在现代计算中的影响,我们可以更好地理解操作系统在计算机科学中的核心地位及其未来发展趋势。
|
3月前
|
Unix Linux 程序员
Unix:Linux的“逗趣祖师爷”与它的不凡传承
在科技长河中,Unix犹如一颗恒星,既是历史见证者也是未来的启发者。1969年,因程序员肯·汤普森想在他的PDP-7上玩“Space Travel”游戏,意外创造了Unix,以简洁优雅的代码改变了操作系统的世界。进入90年代,林纳斯·托瓦兹受Unix启发,开发了开源免费的Linux,像是Unix调皮的孙子,不仅继承其精髓还增添了开放共享的精神。Unix与Linux之间的传承,就像是智者与追蝶孩童的故事,充满了岁月的智慧与新生的活力,提醒我们科技传奇往往源于不起眼的小事。下次使用Linux时,不妨会心一笑吧!
53 0
|
3月前
|
开发框架 Unix Linux
LangChain 构建问题之在Unix/Linux系统上设置OpenAI API密钥如何解决
LangChain 构建问题之在Unix/Linux系统上设置OpenAI API密钥如何解决
48 0
|
6月前
|
Unix Shell Linux
在Linux和类Unix系统中,Shell提供了多种命令用于用户和权限管理
在Linux和类Unix系统中,Shell提供了多种命令用于用户和权限管理
75 4