Linux C编程--fork 详解
分享一下我老师大神的人工智能教程!零基础,通俗易懂!http://blog.csdn.net/jiangjunshow
也欢迎大家转载本篇文章。分享知识,造福人民,实现我们中华民族伟大复兴!
在Linux系统下学习一个系统函数最好的方法就是阅读其源码,首先,给出fork函数的源码
/** linux/kernel/fork.c* //--fork()用于创建子进程* (C) 1991 Linus Torvalds*//** 'fork.c' contains the help-routines for the 'fork' system call* (see also system_call.s), and some misc functions ('verify_area').* Fork is rather simple, once you get the hang of it, but the memory* management can be a bitch. See 'mm/mm.c': 'copy_page_tables()'*/#include <errno.h>#include <linux/sched.h>#include <linux/kernel.h>#include <asm/segment.h>#include <asm/system.h> //--写页面验证,若页面不可写,则复制页面extern void write_verify(unsigned long address);long last_pid=0; //--进程空间区域写前验证函数void verify_area(void * addr,int size){ unsigned long start; start = (unsigned long) addr; size += start & 0xfff; start &= 0xfffff000; start += get_base(current->ldt[2]); //--逻辑地址到线性地址的转换 while (size>0) { size -= 4096; write_verify(start); start += 4096; }}int copy_mem(int nr,struct task_struct * p) //--复制内存页表{ //--由于采用写时复制技术,这里只复制目录和页表项,不分配内存 unsigned long old_data_base,new_data_base,data_limit; unsigned long old_code_base,new_code_base,code_limit; code_limit=get_limit(0x0f); //--取段限长 data_limit=get_limit(0x17); old_code_base = get_base(current->ldt[1]); old_data_base = get_base(current->ldt[2]); if (old_data_base != old_code_base) panic("We don't support separate I&D"); if (data_limit < code_limit) panic("Bad data_limit"); new_data_base = new_code_base = nr * TASK_SIZE; p->start_code = new_code_base; set_base(p->ldt[1],new_code_base); set_base(p->ldt[2],new_data_base); if (copy_page_tables(old_data_base,new_data_base,data_limit)) { //--复制页表 free_page_tables(new_data_base,data_limit); return -ENOMEM; } return 0;}/** 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.*/ //--fork()子程序,它复制系统进程信息,设置寄存器,复制数据段(代码段)int copy_process(int nr,long ebp,long edi,long esi,long gs,long none, long ebx,long ecx,long edx, long orig_eax, 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; *p = *current; /* NOTE! this doesn't copy the supervisor stack */ p->state = TASK_UNINTERRUPTIBLE; p->pid = last_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; 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 ; frstor %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++) //--如果父进程中有文件是打开的,则将对应文件的打开次数增1 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++; if (current->library) current->library->i_count++; set_tss_desc(gdt+(nr<<1)+FIRST_TSS_ENTRY,&(p->tss)); //--在GDT表中设置新任务的TSS和LDT set_ldt_desc(gdt+(nr<<1)+FIRST_LDT_ENTRY,&(p->ldt)); p->p_pptr = current; p->p_cptr = 0; p->p_ysptr = 0; p->p_osptr = current->p_cptr; if (p->p_osptr) p->p_osptr->p_ysptr = p; current->p_cptr = p; p->state = TASK_RUNNING; /* do this last, just in case */ return last_pid;}int find_empty_process(void) //--为新进程取得不重复的进程号last_pid{ 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) || (task[i]->pgrp == last_pid))) goto repeat; for(i=1 ; i<NR_TASKS ; i++) if (!task[i]) return i; return -EAGAIN;}
以下给出说明:
fork函数
#include <sys/types.h>
#include <unistd.h> pid_t fork(void);
fork
调用失败则返回-1,调用成功的返回值见下面的解释。我们通过一个例子来理解fork
是怎样创建新进程的。
这个程序的运行过程如下图所示。
父进程初始化。
父进程调用
fork
,这是一个系统调用,因此进入内核。内核根据父进程复制出一个子进程,父进程和子进程的PCB信息相同,用户态代码和数据也相同。因此,子进程现在的状态看起来和父进程一样,做完了初始化,刚调用了
fork
进入内核,还没有从内核返回。现在有两个一模一样的进程看起来都调用了
fork
进入内核等待从内核返回(实际上fork
只调用了一次),此外系统中还有很多别的进程也等待从内核返回。是父进程先返回还是子进程先返回,还是这两个进程都等待,先去调度执行别的进程,这都不一定,取决于内核的调度算法。如果某个时刻父进程被调度执行了,从内核返回后就从
fork
函数返回,保存在变量pid
中的返回值是子进程的id,是一个大于0的整数,因此执下面的else
分支,然后执行for
循环,打印"This is the parent\n"
三次之后终止。如果某个时刻子进程被调度执行了,从内核返回后就从
fork
函数返回,保存在变量pid
中的返回值是0,因此执行下面的if (pid == 0)
分支,然后执行for
循环,打印"This is the child\n"
六次之后终止。fork
调用把父进程的数据复制一份给子进程,但此后二者互不影响,在这个例子中,fork
调用之后父进程和子进程的变量message
和n
被赋予不同的值,互不影响。父进程每打印一条消息就睡眠1秒,这时内核调度别的进程执行,在1秒这么长的间隙里(对于计算机来说1秒很长了)子进程很有可能被调度到。同样地,子进程每打印一条消息就睡眠1秒,在这1秒期间父进程也很有可能被调度到。所以程序运行的结果基本上是父子进程交替打印,但这也不是一定的,取决于系统中其它进程的运行情况和内核的调度算法,如果系统中其它进程非常繁忙则有可能观察到不同的结果。另外,读者也可以把
sleep(1);
去掉看程序的运行结果如何。这个程序是在Shell下运行的,因此Shell进程是父进程的父进程。父进程运行时Shell进程处于等待状态,当父进程终止时Shell进程认为命令执行结束了,于是打印Shell提示符,而事实上子进程这时还没结束,所以子进程的消息打印到了Shell提示符后面。最后光标停在
This is the child
的下一行,这时用户仍然可以敲命令,即使命令不是紧跟在提示符后面,Shell也能正确读取。
fork
函数的特点概括起来就是“调用一次,返回两次”,在父进程中调用一次,在父进程和子进程中各返回一次。从上图可以看出,一开始是一个控制流程,调用fork
之后发生了分叉,变成两个控制流程,这也就是“fork”(分叉)这个名字的由来了。子进程中fork
的返回值是0,而父进程中fork
的返回值则是子进程的id(从根本上说fork
是从内核返回的,内核自有办法让父进程和子进程返回不同的值),这样当fork
函数返回后,程序员可以根据返回值的不同让父进程和子进程执行不同的代码。
fork
的返回值这样规定是有道理的。fork
在子进程中返回0,子进程仍可以调用getpid
函数得到自己的进程id,也可以调用getppid
函数得到父进程的id。在父进程中用getpid
可以得到自己的进程id,然而要想得到子进程的id,只有将fork
的返回值记录下来,别无它法。
fork
的另一个特性是所有由父进程打开的描述符都被复制到子进程中。父、子进程中相同编号的文件描述符在内核中指向同一个file
结构体,也就是说,file
结构体的引用计数要增加。
给我老师的人工智能教程打call!http://blog.csdn.net/jiangjunshow
Linux C编程--fork 详解相关推荐
- Linux C编程--fork()详解
在Linux系统下学习一个系统函数最好的方法就是阅读其源码,首先,给出fork函数的源码 /* * linux/kernel/fork.c * //--fork()用于创建子进程 * (C) 1991 ...
- Linux网络编程实例详解
本文介绍了在Linux环境下的socket编程常用函数用法及socket编程的一般规则和客户/服务器模型的编程应注意的事项和常遇问题的解决方法,并举了具体代 码实例.要理解本文所谈的技术问题需要读者具 ...
- Linux 网络编程—— libpcap 详解
概述 libpcap 是一个网络数据包捕获函数库,功能非常强大,Linux 下著名的 tcpdump 就是以它为基础的. libpcap主要的作用 1)捕获各种数据包,列如:网络流量统计. 2)过滤网 ...
- Linux Shell 编程基础详解——吐血整理,墙裂推荐!
第一部分:Linux Shell 简介 Shell 是一个用 C 语言编写的程序,它是用户使用 Linux 的桥梁.Shell 既是一种命令语言,又是一种程序设计语言. Shell 是指一种应用程序, ...
- linux c多进程多线程,linux下的C\C++多进程多线程编程实例详解
linux下的C\C++多进程多线程编程实例详解 1.多进程编程 #include #include #include int main() { pid_t child_pid; /* 创建一个子进程 ...
- linux命令大全 美pdf,Linux编程命令详解_10331298_(美)Richard..pdf-得力文库
Linux编程命令详解_10331298_(美)Richard....pdf General Ination 书名Linux编程命令详解 作者(美)Richard Petersen著:梁普选,刘玉芬等 ...
- linux 脚本编写基本命令,Linux Shell命令行及脚本编程实例详解
<Linux典藏大系:Linux Shell命令行及脚本编程实例详解>共15章,分为两篇.主要内容包括:Linux 及Linux Shell简介.初识Linux Shell.常用Shell ...
- 《Linux设备驱动开发详解 A》一一2.3 接口与总线
本节书摘来华章计算机出版社<Linux设备驱动开发详解 A>一书中的第2章,第2.3节,作者:宋宝华 更多章节内容可以访问云栖社区"华章计算机"公众号查看.1 2.3 ...
- linux的strace命令(详解)
linux的strace命令(详解) 本文详细讲述linux下的strace命令的用法. strace 命令是一种强大的工具,它能够显示所有由用户空间程序发出的系统调用. strace 显示这些调用的 ...
最新文章
- 父与子的编程python_父母在人生尚有来处,父母去人生只剩归途!(看一次,哭一次)...
- Docker容器压力测试查看CPU权重
- Facebook全球宕机近7小时,传有15亿用户数据泄漏,市值蒸发千亿
- 2017\National _C_C++_C\1.哥德巴赫分解
- 如何使用XML 配置的方式配置Spring?
- 服务器软RAID和LVM的实现
- 这份 Pandas 学习教程很不错,可在线运行
- linux安装mysql配置,linux安装mysql,配置mysql文件
- JavaScript:Boolean对象
- 苹果设备型号代码 device model id / device codes(更新至iPhone 13 / iPhone SE3 / iPad Air 5代
- 记一次wireshark抓取QQ好友IP和火绒抓取微信IP
- Thymeleaf数据回显
- 射手播放器的 clientkey
- Android 打开淘宝商品详情
- David Lowe 的sift代码
- 咸鱼Maya笔记—Maya 场景操作
- 行逻辑链接的顺序表(压缩存储稀疏矩阵)详解
- 未认证公众号如何跳转其他链接
- 单字节和双字节的转换
- Access时间日期比较查询的方法