分享一下我老师大神的人工智能教程!零基础,通俗易懂!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是怎样创建新进程的。

这个程序的运行过程如下图所示。

  1. 父进程初始化。

  2. 父进程调用fork,这是一个系统调用,因此进入内核。

  3. 内核根据父进程复制出一个子进程,父进程和子进程的PCB信息相同,用户态代码和数据也相同。因此,子进程现在的状态看起来和父进程一样,做完了初始化,刚调用了fork进入内核,还没有从内核返回

  4. 现在有两个一模一样的进程看起来都调用了fork进入内核等待从内核返回(实际上fork只调用了一次),此外系统中还有很多别的进程也等待从内核返回。是父进程先返回还是子进程先返回,还是这两个进程都等待,先去调度执行别的进程,这都不一定,取决于内核的调度算法。

  5. 如果某个时刻父进程被调度执行了,从内核返回后就从fork函数返回,保存在变量pid中的返回值是子进程的id,是一个大于0的整数,因此执下面的else分支,然后执行for循环,打印"This is the parent\n"三次之后终止。

  6. 如果某个时刻子进程被调度执行了,从内核返回后就从fork函数返回,保存在变量pid中的返回值是0,因此执行下面的if (pid == 0)分支,然后执行for循环,打印"This is the child\n"六次之后终止。fork调用把父进程的数据复制一份给子进程,但此后二者互不影响,在这个例子中,fork调用之后父进程和子进程的变量messagen被赋予不同的值,互不影响。

  7. 父进程每打印一条消息就睡眠1秒,这时内核调度别的进程执行,在1秒这么长的间隙里(对于计算机来说1秒很长了)子进程很有可能被调度到。同样地,子进程每打印一条消息就睡眠1秒,在这1秒期间父进程也很有可能被调度到。所以程序运行的结果基本上是父子进程交替打印,但这也不是一定的,取决于系统中其它进程的运行情况和内核的调度算法,如果系统中其它进程非常繁忙则有可能观察到不同的结果。另外,读者也可以把sleep(1);去掉看程序的运行结果如何。

  8. 这个程序是在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 详解相关推荐

  1. Linux C编程--fork()详解

    在Linux系统下学习一个系统函数最好的方法就是阅读其源码,首先,给出fork函数的源码 /* * linux/kernel/fork.c * //--fork()用于创建子进程 * (C) 1991 ...

  2. Linux网络编程实例详解

    本文介绍了在Linux环境下的socket编程常用函数用法及socket编程的一般规则和客户/服务器模型的编程应注意的事项和常遇问题的解决方法,并举了具体代 码实例.要理解本文所谈的技术问题需要读者具 ...

  3. Linux 网络编程—— libpcap 详解

    概述 libpcap 是一个网络数据包捕获函数库,功能非常强大,Linux 下著名的 tcpdump 就是以它为基础的. libpcap主要的作用 1)捕获各种数据包,列如:网络流量统计. 2)过滤网 ...

  4. Linux Shell 编程基础详解——吐血整理,墙裂推荐!

    第一部分:Linux Shell 简介 Shell 是一个用 C 语言编写的程序,它是用户使用 Linux 的桥梁.Shell 既是一种命令语言,又是一种程序设计语言. Shell 是指一种应用程序, ...

  5. linux c多进程多线程,linux下的C\C++多进程多线程编程实例详解

    linux下的C\C++多进程多线程编程实例详解 1.多进程编程 #include #include #include int main() { pid_t child_pid; /* 创建一个子进程 ...

  6. linux命令大全 美pdf,Linux编程命令详解_10331298_(美)Richard..pdf-得力文库

    Linux编程命令详解_10331298_(美)Richard....pdf General Ination 书名Linux编程命令详解 作者(美)Richard Petersen著:梁普选,刘玉芬等 ...

  7. linux 脚本编写基本命令,Linux Shell命令行及脚本编程实例详解

    <Linux典藏大系:Linux Shell命令行及脚本编程实例详解>共15章,分为两篇.主要内容包括:Linux 及Linux Shell简介.初识Linux Shell.常用Shell ...

  8. 《Linux设备驱动开发详解 A》一一2.3 接口与总线

    本节书摘来华章计算机出版社<Linux设备驱动开发详解 A>一书中的第2章,第2.3节,作者:宋宝华 更多章节内容可以访问云栖社区"华章计算机"公众号查看.1 2.3 ...

  9. linux的strace命令(详解)

    linux的strace命令(详解) 本文详细讲述linux下的strace命令的用法. strace 命令是一种强大的工具,它能够显示所有由用户空间程序发出的系统调用. strace 显示这些调用的 ...

最新文章

  1. 父与子的编程python_父母在人生尚有来处,父母去人生只剩归途!(看一次,哭一次)...
  2. Docker容器压力测试查看CPU权重
  3. Facebook全球宕机近7小时,传有15亿用户数据泄漏,市值蒸发千亿
  4. 2017\National _C_C++_C\1.哥德巴赫分解
  5. 如何使用XML 配置的方式配置Spring?
  6. 服务器软RAID和LVM的实现
  7. 这份 Pandas 学习教程很不错,可在线运行
  8. linux安装mysql配置,linux安装mysql,配置mysql文件
  9. JavaScript:Boolean对象
  10. 苹果设备型号代码 device model id / device codes(更新至iPhone 13 / iPhone SE3 / iPad Air 5代
  11. 记一次wireshark抓取QQ好友IP和火绒抓取微信IP
  12. Thymeleaf数据回显
  13. 射手播放器的 clientkey
  14. Android 打开淘宝商品详情
  15. David Lowe 的sift代码
  16. 咸鱼Maya笔记—Maya 场景操作
  17. 行逻辑链接的顺序表(压缩存储稀疏矩阵)详解
  18. 未认证公众号如何跳转其他链接
  19. 单字节和双字节的转换
  20. Access时间日期比较查询的方法

热门文章

  1. Django 流式响应中文csv样例
  2. 全栈JVM框架Micronaut通向1.0版本之路
  3. 【总结整理】关于切图
  4. hdu 1053 Entropy (哈夫曼树)
  5. usermod命令的一些用法详解
  6. (转)android 在电脑上显示真机屏幕
  7. salt-api https证书报错解决方法
  8. 使用Visual Studio 2008 进行远程调试
  9. 文本比较算法Ⅶ——线性空间求最长公共子序列的Nakatsu算法
  10. GARFIELD@07-08-2005 DILBERT