免费注册 查看新帖 |

Chinaunix

  平台 论坛 博客 文库
123下一页
最近访问板块 发新帖
查看: 8493 | 回复: 29
打印 上一主题 下一主题

Linux2.4内核内部分析报告(初稿4)[译文][进行中] [复制链接]

论坛徽章:
0
跳转到指定楼层
1 [收藏(0)] [报告]
发表于 2005-10-06 15:46 |只看该作者 |倒序浏览
一个非常好的文档,这份文档是作者高桥浩和在分析Linux内核2.4.0-test10时所著,最新版本为初稿4(draft4),成稿时间2000.12.9.由于描述的内核版本比较老,部分内容并不适合现在,但其根本并没有改变。这个文档虽然是分析内核,但主要说的linux内核的运行机制及用伪C代码描述的系统级别函数的实现方法,我觉得对理解UNIX中C函数的具体机理及其所代表的具体含义有很大的帮助作用,这个文档虽然不能帮助我们达到“知其所以然”的境界,但从另一个角度看不但能让我们在短时间内对Linux内核的具体实现有个大概的了解,而且对在Linux下编程的我们来说其意义更在于了解函数与函数间是如何相互作用。更安全,更准确,更高效地去使用它们。

    我翻译这个文档出于学习目的,由于文档本身并没有版权声明,因此这个翻译版本也没有得到作者的任何授权,在此声明。另外,我也是边翻译边学习,由于自己知识极端不足,我无法保证(虽然尝试)翻译的正确性,加上由于我毫无任何文字功底可言,所使用的文法也可能造成你阅读困难,在此先道歉,最后还有一点就是,由于我从接触计算机知识时就不具备阅读中文书籍的条件,因此我对专业术语的中文对应词汇的知识仅来自于论坛,如果所用词汇不符和大众习惯,请忍耐,如果影响了文档的正确性,请在论坛留言,谢谢。

--------------------------------------------------------------------------------------------------
    文档按顺序由“执行管理”,“文件系统”,“内存管理”,“网络”(相当精彩),“系统引导”,“多处理器管理”六个部分组成。我会先完成“执行管理”部分,之后是“内存管理”,之后是“网络”或“文件系统”...

目录


  1. 第一部分:

  2.         执行管理
  3.                 [color=blue]。进程管理
  4.                         。进程模型
  5.                                 。构成进程的资源
  6.                         。进程的状态迁移
  7.                         。进程的一生
  8.                                 。fork
  9.                                 。exec
  10.                                 。exit
  11.                         。进程的调度
  12.                                 。调度器
  13.                                 。进程的切换
  14.                                 。进程的同期
  15.                         。抢占处理
  16.                         。信号灯
  17.                         。其他的与调度有关的函数的说明
  18.                         。进程的父子关系
  19.                         。进程ID
  20.                                 。函数说明
  21.                         。信号
  22.                                 。函数说明
  23.                                 。信号的忽视及屏蔽
  24.                                 。SIGCHLD信号
  25.                                 。延缓信号
  26.                                 。与信号有关的数据结构及其他函数
  27.                         。线程[/color]
  28.                 。延迟处理
  29.                         。软中断处理函数
  30.                                 。函数说明
  31.                         。BH处理函数
  32.                                 。函数说明
  33.                         。任务队列
  34.                                 。函数说明
  35.                 。计时器
  36.                         。时钟控制
  37.                         。计时器列表
  38.                                 。函数说明
  39.                         。其他与计时器相关的功能
  40.                                 。内核内的时限功能
  41.                                 。settimer系统调用
  42.                 。中断控制
  43.                         。中断控制函数
  44.                         。中断控制函数的注册
  45.                         。中断控制函数的起动
  46.                                 。问题点
  47.                         。禁止中断
  48.                                 。CPU级别的中断控制
  49.                                 。补充
  50.                                 。中断控制器级别的中断控制
  51.                 。内核服务的入口
  52.                         。系统调用的入口
  53.                         。中断入口
  54.                         。页访问失败
  55.                         。一般异常
  56.         。文件系统
  57.                 。
  58.                 。
  59.                 。
复制代码


进程模型


  1.     linux中的所有的进程都由task_struct构造体管理。在生成进程的时候将会分配一个task_struct构造体,之后将通过这个构造体对进程进行管理。
  2.     与传统的UNIX不同,linux并不存在user构造体,进程的管理情报全部存放于task_struct构造体中。task_struct构造体存在于平坦地址空间内,任何时候Linux内核都可以参照所有进程的所有管理情报。内核堆栈也同样位于平坦地址空间内。
  3.     (传统的UNIX将不被别的进程参照的数据及内核堆栈放在各个进程独立的U构造体内。)
复制代码


构成进程的资源


  1.     以下为今后说明时使用的task_struct构造体的主要成员。

  2. 主要成员:
  3.      struct task_struct {
  4.           struct files_struct* files; //文件描述符
  5.           struct signal_struct* sig;  //信号控制signal handler
  6.           struct mm_struct* mm;       //内存管理模块

  7.           long stat                                         //进程状态
  8.           struct list_head runlist;                    //用于联结RUN队列
  9.           long  priority;             //基本优先权
  10.           long  counter;              //变动优先权

  11.           char comm[];                                    //命令名
  12.           struct thread_struct tss;   //上下文保存领域
  13.      };
  14. 当使用fork生成新的进程的时候,将复制所有的资源。但是内存领域使用Copy-On-Write,只到进行写操作时才进行复制处理。对文件则只负责复制文件描述符以共用file结构体。依赖于这种策略,由shell起动的命令可以什么都不考虑而使用标准输入输出及利用管道和重定向。

复制代码

进程的状态迁移

  1.     进程的状态为以下中的一种。CPU上可执行的进程状态为TASK_RUNNING。CPU将分配给多个TASK_RUNNING状态进程中优先权最高的。
  2.     TASK_INTERRUPTIBLE和TASK_UNINTERRUPTIBLE都是为等待某个条件成立而中断的状态(发送硬盘I/O要求而等待I/O完成的状态,等待TTY终端的输入的状态等)。等待状态分为两种是为了控制在等待期间接受到信号时是否解除等待状态。以TASK_INTERRUPTIBLE状态经入等待的情况下,可被强制唤醒。(等待硬盘I/O时,为了操作完成为止延迟操作信号,使用TASK_UNINTERRUPTIBLE,而等待无法保证唤醒时间的TTY终端I/O的时候,使用TASK_INTERRUPTIBLE)。
  3.     TASK_STOPPED状态表示接受到延缓信号(SIGSTOP,SINGTTIN等)而进入中断状态。这种状态的进程将不会成为进程切换的对象。在接受到恢复信号(SIGCONT)后返回TASK_RUNNING状态并成为切换对象。
  4.     TASK_ZOMBIE状态为exit后到消亡为止之间的进程状态。进程在结束后到父进程执行wait之间,成为TASK_ZOMBIE状态并持续存在于系统中。
  5.     最终,linux没有实现进程全体的swap,曾在头文件中定义的TASK_SWAPPING状态在版本2.4中被删除了。
  6.    



  7.         
  8. 状态                                                         说明
  9. TASK_RUNNING                                  执行可能状态
  10. TASK_INTERRUPTIBLE                        等待状态。可接受信号
  11. TASK_UNINTERRUPTIBLE                    等待状态。不能接受信号
  12. TASK_ZOMBIE                                     僵尸状态。exit后的状态
  13. TASK_STOPPED                                   延缓状态

  14. Linux中定义的状态是直线形的,不存在类似既是等待又是延缓的状态。
  15. Linux中,对等待状态中的进程发送SIGSTOP等信号的话将记录对其有延缓请求,当进程转为TASK_RUNNING时,根据记录中的请求将其转为TASK_STOPPED。
复制代码

进程的一生

   fork
  1.   
  2.     之后将讲述的线程也完全一样使用do_fork函数生成。只是在于呼叫这个函数时传递的参数不同。

  3.         do_fork(flag,进程上下文)
  4.         分配一个空的task_struct(alloc_task_struct函数)
  5.         分配一个进程ID(get_pid函数)
  6.         初始化task_struct构造体的各个成员
  7.         复制文件描述符表(copy_files函数)
  8.         当前目录,umask等的复制(copy_fs函数)
  9.         复制信号情报(copy_sighand函数)
  10.         父进程上下文的复制(copy_thread函数)
  11.         使用Copy-On-Write复制虚拟内存空间(copy_mm函数)
  12.         将生成的子进程联结入RUNQ(wake_up_process函数)
复制代码

exec

  1.     进程可使用exec系统调用执行新的命令。exec系统调用将一次性释放全部虚拟内存空间,之后生成新的空间并将新的命令影射入内。

  2.     do_execve(文件路径,参数。环境)
  3.         打开文件(open_namei函数)
  4.         计算exec后的UID/GID,读入文件头(prepare_binprm函数)
  5.         读入命令名,环境变量,起动参数(copy_strings函数)
  6.         呼叫各种不同二进制文件的操作函数(search_binary_handler函数)

  7.     ELF格式的话,经由search_binary_handler函数呼叫load_elf_binary函数。如果是动态联结,同时影射动态联结器(ld*.so)

  8.     load_elf_binary(linux_binprm* bprm,pt_regs* regs)
  9.         分析ELF文件头
  10.         读入程序的头部分(kernel_read函数)
  11.         if(存在解释器头部){
  12.                 读入解释器名(ld*.so)(kernel_read函数)  |(zalem note:可用
  13.                 打开解释器文件(open_exec函数)                | objdump -s -j .interp xxx
  14.             读入解释器文件的头部(kernel_read函数)   |命令查看,
  15.         }                                                          |linux下是/lib/ld-linux.so.x)
  16.         释放空间,清楚信号,关闭指定了close-on-exec标识的文件(flush_old_exec函数)
  17.         生成堆栈空间,塞入环境变量/参数部分(setup_arg_pages函数)
  18.         for(可引导的所有的程序头){
  19.                 将文件影射入内存空间(elf_map,do_mmap 函数)
  20.         }
  21.         if(为动态联结){
  22.                 影射动态联结器(load_elf_interp函数)
  23.         }
  24.         释放文件(sys_close函数)
  25.         确定执行中的UID,GID(compute_creds函数)
  26.         生成bss领域(set_brk函数)
  27.         bss领域清零(padzero函数)
  28.         设定从exec返回时的IP,SP(start_thread函数)(动态联结时的IP指向解释器的入口)
复制代码

exit

  1.     进程的结束由do_exit函数进行。除显式呼叫exit系统函数外,接受到信号而终止的情况下也被调用。do_exit函数释放除task_struct构造体外的所有资源。do_exit使用exit_notify函数向父进程发送SIGCHLD信号。接受到SIGCHLD函数的父进程找出成为ZOMBIE状态的子进程,释放其task_struct.

  2.         do_exit(终止号)
  3.         {
  4.                 停止此进程的计时器(del_timer_sync函数)
  5.                 释放IPC信号灯(sem_exit函数)
  6.                 释放虚拟空间(__exit_mm函数)
  7.                 关闭文件及释放管理空间(__exit_files函数)
  8.                 释放当前目录,umask情报(__exit_fs函数)
  9.                 抛弃信号及管理领域的释放(__exit_sighand函数)
  10.                 将进程状态设为TASK_ZOMBIE
  11.                 通知父进程(exit_notify函数)
  12.                 放弃CPU(schedule函数)
  13.         }

  14.     父进程使用下面的release函数释放成为了ZOMBIE的task_struct构造体。
  15.         release(ZOMBIE的子进程)
  16.                 释放PID(unhash_process函数)
  17.                 释放task_struct(free_task_struct函数)
复制代码

进程调度

调度器

  1.     调度器对进程和线程同等对待。执行可能状态的进程(线程)连接入下图的RUN队列。调度器在连入RUN队列中选出优先度最高的进程并将CPU(执行权)交给它。叫做current的指针指向现在执行中的进程。
  2.     如果没有任何可执行的进程,调度器将执行权交给叫做idle的什么也不作的进程。

  3.         schedule()
  4.                 执行任务队列 – tq_scheduler(run_task_queue函数) <后述>;
  5.                 呼叫BH handler(do_softirq函数)  <后述>;
  6.                 拒绝抢占请求 (zalem note: cli)
  7.                 if(调度器呼叫出的进程状态并非TASK_RUNNING){
  8.                         将进程从RUN队列中清除(del_from_runqueue函数)
  9.                 }
  10.                 while(对接入RUN队列的所有进程){
  11.                         搜索优先度最高的进程(goodness函数)
  12.                 }
  13.                 while(对系统中所有的进程){
  14.                         重新计算优先度
  15.                 }
  16.                 切换进程上下文(switch_to函数)

  17.     补充:
  18.         Linux的调度器十分的简单。不但RUN队列只有一个而且每次都进行线性查找,因此在巨大系统中可能有些不高效。
复制代码

进程切换

  1.     将现在运行中的进程的上下文(CPU状态-即寄存器情报)保存,将下一个要运行的进程的上下文读入CPU的工作,称为进程切换。运行再开的时候,将先前保存于内存中的上下文传入CPU的话。就可以再次从中断处开始运行。
  2.     Linux中,switch_to担负此项工作。上下文的储存领域则使用进程的内核堆栈及struct_task中的领域(tss部分 – 关于tss的名称请参照Inter CPU的手册  zalem note:Task State Segment)

复制代码

进程的同期

  1.     运行中的进程经入等待状态的话,将自身连入为每个等待对象(zalem note:指硬盘I/O操作,串口输入输出等,称为等待对象)准备的wait队列的头部并放弃CPU。为此(自己将自己从RUN队列中脱离,并呼叫调度器)准备了sleep_on函数,interruptible_sleep_on函数。这两个函数的区别在于成为WAIT状态时进程能否被信号唤醒。

  2.         sleep_on(WAIT队列头)(或者是interruptible_sleep_on(WAIT队列头))
  3.                 在堆栈上准备wait_queue
  4.                 将当前进程的状态转为TASK_UNINTERRUPTIBLE
  5.                         (interruptible_sleep_on的话是TASK_INTERRUPTIBLE)
  6.                 在wait_queue中注册当前进程
  7.                 将wait_queue连入WAIT队列头部(add_wait_queue函数)
  8.                 呼叫调度器以放弃CPU(schedule函数)
  9.                 将wait_queue脱离WAIT队列(remove_wait_queue函数)

  10.     这个进程将会被事件的发生所唤醒。(wake_up函数,wake_up_interruptible函数等),进程被唤醒时将被连入RUN队列,但此时仍存在于wait队列中。当这个进程再度获得执行权时,首先要作的就是将自己从wait队列中清除。
  11.     这种方式的好处在于,由于中断控制函数(handler)的延长逾期而导致被唤醒的时候,并不用进行wait队列的操作,并可简化互斥处理。

  12.         __wake_up(WAIT队列头,mode)
  13.                 while(对在WAIT队列中等待的所有的进程){
  14.                         如果进程属性同mode指定的一致(TASK_INTERRUPTIBLE等)
  15.                                 则唤醒(__wake_up_process函数)。
  16.                         如果指定为只唤醒一个(TASK_EXCLUSIVE),则break;
  17.                 }

  18.         __wake_up_process(进程)
  19.                 将进程状态改为TASK_RUNNING
  20.                 if(这个进程还没有连入RUN队列)
  21.                         连入RUN队列(add_to_runqueue函数)
  22.                         要求重新调度(reschedule_idle函数)
  23.                 }

  24.     到版本2.2为止,wake_up函数将WAIT队列中所有成为对象的进程都设为RUN状态,出于性能改善的目的,从版本2.4开始可以只将WAIT队列中位于头部的进程设为RUN状态。这用于后面将提到的信号灯及负荷较集中的地方。如果进程属性中加入TASK_EXCLUSIVE,则仅唤醒先头的进程。
  25.     __wake_up()函数被外包以方便使用及与版本2.2的兼容

  26.       。wake_up(WAIT队列头)
  27.                 。唤醒TASK_UNINTERRUPTIBLE,TASK_INTERRUPTIBLE两个属性的进程。但仅唤醒位于先头的一个(TASK_EXCLUSIVE)。(唤醒了具有TASK_EXCLUSIVE属性的进程后,将不会唤醒其后的进程)
  28.       。wake_up_all(WAIT队列头)
  29.                 。唤醒所有TASK_UNINTERRUPTIBLE,TASK_INTERRUPTIBLE的等待进程(忽略TASK_EXCLUSIVE属性)
  30.       。wake_up_interruptible(WAIT队列头)
  31.                 。唤醒TASK_INTERRUPTIBLE属性的进程。但仅唤醒位于先头的一个(TASK_EXCLUSIVE)。(唤醒了具有TASK_EXCLUSIVE属性的进程后,将不会唤醒其后的进程)
  32.       。wake_up_interruptible_all(WAIT队列头)
  33.                 。 唤醒所有TASK_INTERRUPTIBLE的等待进程

  34.     补充说明1:
  35.                 虽然一般情况下是调用sleep_on函数,或interruptible_sleep_on函数经入等待,但在linux内核中很多地方是将sleep_on函数展开,自己操作wait队列,自己改变进程状态及呼叫调度器。
  36.     补充说明2:
  37.                 传统的UNIX在sleep中由信号强制唤醒的话,默认状态为直接长跳转(zalem note:longjmp()|siglongjmp())到系统调用的出口并返回EINTR,而Linux下只是普通的唤醒,不进行长跳转。
复制代码


抢占处理

  1.     当进程被wake_up_process等函数改变为执行可能的时候,被连接入RUN队列。但是如果只是仅仅连入RUN队列的话,这个无论这个进程的优先度多高都不会获得CPU。
  2.     当这个进程的优先权大于当前运行中的进程的话,必须对调度器显式提出获取CPU的要求(抢占请求)(reschedule_idle函数)(如果优先度相差很小的话则例外)。抢占请求是靠将当前运行的进程的task_strcut的need_resched成员作记号来实现的。(在支持多处理器前,好像是系统的全局变量。虽然在多处理器化后改为进程单位了,但其实应该以CPU为单位)。
  3.     接受到抢占请求的调度器在Linux内核执行到某个段落之后进行调度(schedule函数)。再调度的执行在下几个点。
  4.         。系统调用完成时
  5.         。中断处理完成时
  6.         。idle时
  7.     另外,这意味着linux的内核内运行时不可被抢占(这也意味着当Linux用于实时系统时无法获得好的反应性。即使是实时性的进程也无法在内核执行时获得CPU)(zalem note:2.4!=2.6)。这利于简化Linux内核内的资源互斥(Mutual Exclusion)操作。
复制代码

信号灯

  1.     为获得/等待资源,Linux提供了信号灯操作函数。在需要等待的类型的资源内定义semaphore型成员,对这个成员进行down函数,up函数操作来完成资源获得和资源解放工作。用down函数获得资源失败的话,到(别的进程用)up函数解放资源为止,处于WAIT状态。
  2.     到版本2.2为止,up并不进行复杂处理而是唤起所有的进程,被唤醒的进程依靠先到先得的规则取得(down)信号灯,从版本2.4开始,则改为只唤起位于先头的一个进程。
  3.     下面显示down函数和up函数的大概的流程(事实上为了对应SMP包含了很多复杂的汇编代码)。
  4.      作为Linux的功能,系统还提供了进程能被信号唤醒的函数(down_interrutptible)和如果信号灯在请求时能获得则获得的(down_trylock)函数。

  5.         down(信号灯)
  6.                 if(信号灯的值还有剩余)
  7.                         信号灯的值减一,return OK;
  8.                 在堆栈上准备wait_queue
  9.                 当前进程状态改为TASK_UNINTERRUPTIBLE|TASK_EXCLUSIVE
  10.                 在wait_queue中注册当前进程
  11.                 将信号灯连接入wait_queue(add_wait_queue_exclusive函数)
  12.                 while(1){
  13.                         if(信号灯的值为0){
  14.                                 呼叫调度器放弃CPU(schedule函数)
  15.                                 进程状态改为TASK_UNINTERRUPTIBLE|TASK_EXCLUSIVE
  16.                         }else{
  17.                                 信号灯的值减一
  18.                                 将进程状态改为TASK_RUNNING
  19.                                 将信号灯从wait_queue分离(remove_wait_queue函数)
  20.                                 return
  21.                         }
  22.                 }

  23.         up(信号灯)
  24.                 信号灯的值加一
  25.                 将等待这个信号灯的进程中处于先头的进程唤醒(wake_up)

  26.     补充说明
  27.         XXX:在性能,效率方面都很好,但进程优先度方面还有问题。本来,唤醒的应该是等待这个信号灯的进程中优先度最高的那个。
复制代码

其他与进程调度相关的函数的说明

  1.         。reschedule_idle()
  2.                 。指定的进程的优先度高于当前运行进程的话,向调度器提交抢占请求
  3.         。goodness()
  4.                 。取得指定进程的优先度
  5.         。add_to_runqueue()
  6.                 。将进程放入RUN队列先头
  7.         。del_from_runqueue()
  8.                 。将进程从RUN队列中清除
  9.         。move_last_runqueue(),move_first_runqueue()
  10.                 。将进程移到RUN队列的最后或最前
  11.         。add_wait_queue(WAIT队列头)
  12.                 。将进程连入WAIT队列的先头
  13.         。add_wait_queue_exclusive(WAIT队列头)
  14.                 。将进程放入WAIT队列的最后
  15.         。remove_wait_queue()
  16.                 。将进程从WAIT队列中清除
  17.         。wake_up_process_synchronous()
  18.                 。基本等同于wake_up_process(),但不发出抢占请求
  19.         。wake_up_sync(),wake_up_interruptible_sync()
  20.                 。和wake_up(),wake_up_interruptible()相同,但不发出抢占请求。用于管道的处理等处理完成之前不发送抢占请求从而对性能有利的地方。
复制代码


进程的父子关系

  1.     各个进程之间有父子关系。这是为了实现父进程等待(wait系统调用)子进程的终止。
  2.     使用fork系统调用生成子进程时,进程的task_struct间持有如下图所示的连接构造。如果父进程先终止的话,子进程们的父进程被换为init进程。唯有init进程没有父进程。
复制代码

进程ID

  1.     作为标识,各个进程都有进程ID。进程ID为进程生成时(fork)时由Linux内核分配的唯一值。另外,除进程ID外,进程还持有进程组ID,会话ID。
  2.     这些ID一般按下图中的方法使用。这个样子进行进程组操作的是Shell。先以Shell为头打开会话,在会话中为每个工作(job)(由管道连接的命令群)制作一个进程组。这部分的操作根据Shell种类的不同而不同。
  3.    另外,为了防止与其他进程相互干涉,各种守护进程一般拥有自己的会话。
  4.     进程组ID,会话ID并不是自由分配的,必须遵守以下规则:
  5.         。会话首领兼任进程组首领。(会话首领无法改变进程组)
  6.         。会话中持有多个进程组。进程可以生成新的进程组(将自己的PID作为组ID)。进程可移动到存在于会话中的任何一个进程组。


  7.     老的BSD系统中没有会话的概念从而有了一个很小的安全漏洞。在导入工作控制功能前,进程组就相当于现在UNIX的会话。
复制代码

函数说明

  1.         。sys_setpgid()
  2.                 。变更进程的进程组ID。可指定的ID要么为这个进程的进程ID,或为存在于这个进程所属的会话内进程组ID。
  3.         。sys_getpgid(),sys_getpgrp()
  4.                 。取得进程的进程组ID
  5.         。sys_setsid()
  6.                 。进程打开新的会话。会话ID,进程组ID为调用函数的进程的进程ID。
  7.         。sys_getsid()
  8.                 。取得进程的会话ID。
复制代码

信号

  1.     作为向进程发送非同期事件通知的方法,Linux提供了叫做信号(Signal)的手段。
  2.     当进程发生了异常时,Linux内核将其转换为信号通知进程。另外,为了通知内核内所发生的事件,Linux内核也会生成信号。(pipe,tty等经常用到)。还有,进程可使用kill系统调用显式的生成信号。
  3.     并不是说当向一个进程发送信号后(send_sig函数),立刻将对象进程清除或起动对象进程注册了的信号处理函数。发送信号的一方只是保证通知,(kill系统调用完成并不表示这个时候对应的进程已经终止。常常可以看到这种理解错误的代码。)处理信号的部分全部在对象进程的上下文中完成(do_signal函数)。
  4.     信号的生成既可能是由异常(存取内存0地址等),pipe/tty状态变更等由Linux内核自动生成的,也可由用户进程发行kill系统调用显式生成。对象进程处于等待状态的时候也可能被强制唤醒(wake_up_process)。
  5.     进程是否接收到信号将在系统调用出口/异常控制函数出口/中断控制函数出口处被检测(与软中断的检测(zalem note:int 0x80)基本相同)。如果收到了信号,则开始处理(do_signal)。信号处理函数(do_signal)中,根据条件的不同,终止进程或为执行用户的信号控制函数作准备。关于用户信号控制函数的执行的实现方式,请参照下图及函数说明。无论在什么情形下产生信号(下图中的signal A B),操作信号的过程都是一样的 。
复制代码

函数说明

  1.     下面将根据处理的流程来说明承担主要任务的函数群。

  2.         。sys_signal(sig,handler),do_sigaction(sig,action..)
  3.                 。注册信号种类为sig的信号操作函数handler。注册处为进程固有的信号处理函数表(struct signal_struct),各个的进程的进程表(struct task_strcut)中对其进行连接。
  4.                 。do_sigaction函数可以对信号控制函数作更细节性的指定。
  5.         。send_sig(sig,p)
  6.                 。向p进程发送sig信号
  7.                 。当信号控制函数指定为忽视(SIG_IGN)时,什么都不作。但,当信号为SIGCHLD时,即使指        定为忽视(SIG_IGN)也发送。
  8.                 。如果信号没有被屏蔽(mask),设对象进程为信号保留状态(task_struct中的sigpendig 标识设为on)。当信号发送的对象进程处于等待状态并且能接收中断的话(TASK_INTERRUPTIBLE),         将被强制性唤醒(wake_up_process函数)。
  9.         。do_signal()
  10.                 。处理被送往对象进程的信号。因信号种类的不同而不同。
  11.                 。如果指定为默认(SIG_DEL)处理,一般的信号将终止进程(do_exit函数)。在处理有的信号时,在进行进程终止处理前先生成core文件(do_coredump函数)。
  12.                   但如果信号为延缓类型则是将进程转为TASK_STOPPED状态。
  13.                 。信号为SIGCHLD并且指定为忽视(SIG_IGN)的话,进行成为僵尸的子进程的释放处理(sys_wait函数)。
  14.                 。如果对象进程注册了信号处理函数,则为进程调用信号处理函数作准备工作。(handle_signal函数)
  15.         。handle_signal()...(参照上图)
  16.                 。在进程空间中准备一个执行信号控制函数用的堆栈并对其进行初始化(setup_frame)。虽然执行信号的专用领域也可用于构造此堆栈(如果登录了的话),但一般情况下在进程的用户堆栈中分配领域。
  17.                         。复制内核堆栈中的此进程的各种上下文到信号处理函数的执行用堆栈中。(在执行指定为再执行(zalem note:sigaction->;SA_RESTART)的系统调用过程中再次收到信号的话,则根据系统调用命令(INT命令)的大小(size)修改(rewind)返回地址。
  18.                         。制造内核堆栈中的进程上下文的各种情报。返回地址(eip (zalem note:PC))改为用户定义的信号处理函数,用户堆栈(esp)修改为前面分配的信号处理栈的值。 这样,当这个进程(从内核级)返回到用户模式时将从信号处理函数处开始执行。(zalem note:esp指向栈顶,因此当执行push %ebp;movl %esp %ebp;后,就算建立了handler自己的stack frame,用于存取参数,返回及display(语言的上级函数变量参照,gdb->;backtrace等使用)。)
  19.                         。为了当用户定义的信号控制函数执行完毕时,自动呼叫sigreturn系统调用,修改此信号堆栈。在栈底处复制入呼叫sigreturn系统调用的指令(有些野蛮)并将地址装入信号堆栈的顶部。这样,当从用户定义的信号控制函数返回时将自动执行存放了sigreturn系统调用的指令。
  20.                 。改变进程的信号屏蔽状态。一般情况下,在执行信号控制函数时将不接受同种信号。(根据注册信号控制函数时指定的不同而不同)(zalem note:sigaction->;SA_NODEFER)
  21.         。sys_sigreturn()...(参照上图)
  22.                 。当用户的信号控制函数结束时将自动呼叫sigreturn系统调用。
  23.                 。将保存于信号堆栈中的进程上下文恢复至内核堆栈的上下文中。这样当进程从Linux内核(sigreturn系统调用)返回时,可以从接受到信号前的状态开始执行。
  24.                 。信号控制函数可以存取堆栈中的上下文领域(接收到信号时的寄存器群的拷贝)。
复制代码

信号的忽视及屏蔽(阻塞 block)

  1.     在需要忽视信号的场合,可以在signal系统调用中显式地指定。send_sig函数在收到信号后将检测是否指定为忽视,如果是则抛弃这个信号。
  2.    
  3.     信号的屏蔽由sys_sigprocmask控制。被屏蔽的信号记录于task_struct的blocked成员中。send_sig函数会将信号传送给对象进程并记录于task_struct的signal成员中,但在对象进程屏蔽此信号的过程中将不会呼叫处理接收信号的do_signal函数。
复制代码

SIGCHLD信号

  1.     进程终止时SIGCHLD信号会被送往其父进程。对SIGCHLD的处理与其他信号不同。
  2.     接收到SIGCHLD的进程的接收信号处理函数(do_signal函数)作以下行为。
  3.         。默认(SIG_DFL)时,在发送端(send_sig函数)就将不作任何处理直接抛弃此信号。
  4.         。指定为忽视(SIG_IGN)时,释放成为僵尸状态的子进程的资源(sys_wait4函数)。由于处理全部在Linux内核内部完成,因此对用户进程来说是透明的。
  5.         。如果注册了控制函数,则和普通的信号同样对待。

  6.     如果没有等待(wait系统调用)子进程结束的必要的话,只要指定SIGCHLD为忽视(SIG_IGN),则系统中不会增加无意义的僵尸进程。

复制代码

延缓(暂停,suspend)信号

  1.     延缓信号用于暂时将某个进程设为中断状态(TASK_STOPPED)。这类信号有以下4种。tty驱动程序生成的SIGSTP,SIGTTIN,SIGTTOU3种和kill系统调用显式生成的SIGSTOP信号。

  2.     SIGCONT用于再开处于中断状态(TASK_STOPPED)进程。

  3.     它们各自的行为如下:
  4.         。SIGSTP,SIGTTIN,SIGTTOU,SIGSTOP信号
  5.                 。指定为忽视(SIG_IGN)的时候则什么都不作(send_sig函数)
  6.                 。接收到信号的进程被信号接收处理函数(do_signal函数)设为TASK_STOPPED状态并中断执行。(调用schedule函数放弃CPU)
  7.         。SIGCONT信号
  8.                 。由信号送信函数(send_sig函数)将TASK_STOPED进程唤起(使之成为TASK_RUNNING)
  9.                 。接收到SIGCONT的进程从被信号接收处理函数(do_signal函数)中断的地方开始执行。如果注册了对应SIGSTP,SIGTTIN,SIGTTOU,SIGSTOP的信号控制函数的话,则起动之(do_signal函数)。vi在延缓后再次恢复时,画面的重绘即是利用了这个。
复制代码


与信号相关的数据结构及其他函数[color]


  1.         struct task_struct{
  2.                 .
  3.                 .
  4.                 int sigpending;        //保留中的需处理信号
  5.                 sigset_t signal;        //接收到的信号
  6.                 sigset_t blocked;        //被屏蔽(阻塞)的信号
  7.                 signal_struct *sig;//指向信号处理函数注册表的指针
  8.                 .
  9.                 .
  10.         }

  11.         。sigaddset(),sigdelset()
  12.                 。操作信号接收的标识(flag)的ON/OFF
  13.         。sigaddsetmask(),sigdelsetmask()
  14.                 。操作信号屏蔽的标识的ON/OFF
  15.         。sigismember()
  16.                 。检测对应于指定信号的标识位是否为ON
  17.         。kill_someting_info(),kill_proc(),kill_pg()
  18.                 。向指定的进程,指定的进程组中的进程发送信号。
  19.                 。也准备了向属于会话的所有进程组发送信号的kill_sl()。
  20.         。sys_sigprocmask()
  21.                 。变更信号的屏蔽(mask)。(利用sigaddsetmask函数,sigdelsetmask函数)

  22.     补充: 最初,UNIX信号的机制,从kill系统调用的名字中也可知道是被设计为强制杀死一个进程这种程度的。
  23.            之后,信号机制不断被扩充而具有了各种各样的功能。但是,虽说站在用户角度看来很便利,其不联贯的机能扩张导致内核的信号控制的实现部分十分混乱。
  24.            最近扩张的功能为,当多个信号连续发生时,将按其产生顺序呼叫信号处理函数。
复制代码

线程

  1.     在linux中作为执行单位的进程和线程是以完全相同的方式实现的。(以扩张进程来实现线程)

  2.     线程与进程不同的只是,它们共用进程所持有的各种内核内的资源。下面显示了由fork生成新进程时的数据结构和由clone生成新的线程是的数据结构。

  3.     当fork生成新的进程的时候,将会同时进行所有资源的复制。但是由于将内存设定为Copy-On-Write,到下次对资源进行写操作时为止并不执行复制处理。对文件则仅复制文件描述符以共用file构造体。依赖于这种策略,由shell起动的命令可以什么都不考虑而使用标准输入输出及利用管道和重定向。

  4.     而另一方面,由clone生成线程是,如下图所示那样,完全不进行资源的复制。两个线程的上下文完全参照同样的资源。不过除此点之外则于进程完全相同。
  5.     同样,对调度器来说两者也完全没有区别。即使是共有同一个内存空间的线程(同一个进程中的线程),在多处理器环境下也可以同时执行于不同的处理器。

复制代码

---------------------待续----------------------

论坛徽章:
0
2 [报告]
发表于 2005-10-06 16:32 |只看该作者

Linux2.4内核内部分析报告(初稿4)[译文][进行中]

顶后再看

论坛徽章:
0
3 [报告]
发表于 2005-10-07 09:57 |只看该作者

Linux2.4内核内部分析报告(初稿4)[译文][进行中]

这部分翻得糟糕,基本上都是我不知道的东西...


。延迟处理
       


  1.     Linux内核准备了将执行延迟的机制。主要利用于缩短硬件中断延长时的控制函数的执行时间来提高反应能力,或简化互斥处理等目的。
复制代码



。软中断处理函数


  1.     linxu将硬件中断处理分为需要高反应性的部分和不需要高反应性的部分两个阶段。时钟处理,TCP/IP协议处理等使用软中断处理。这种处理函数将在linux内核执行到某个时机(系统调用的出口,中断处理函数的出口,调度器)时被执行。
  2.     原来Linux使用一种叫BH处理函数(BH handler)机制,用一种叫作BH处理函数的来完成延迟处理。但是,由于这种机制在在多处理器系统上不能发挥良好的性能,为了强化对SMP的支持,作为对旧的BH处理函数的代替,设计了软中断处理函数。也许在单处理器上可能会感觉不到其优势,但与旧的BH处理不同,SMP上软件中断处理函数可以工作于多线程,因此可以期待性能的上升。例如,网络处理函数等被从BH处理改写为软件中断处理。这将在多处理器那一章详细说明。
  3.     当前定义了4个级别的软解中断。当发出软件中断的请求(__cpu_raise_softirq函数)后,稍后将呼叫do_softirq函数,这个函数中调用注册于各个级别的软件中断处理函数。
  4.         。TASKLET_SOFTIRQ
  5.                 。注册通用处理函数的处理机制。相当于迄今为止的任务队列(后述)。可注册多个。没注册上限。
  6.                 。通用的处理函数被task_let_schechedule函数按FIFO规则注册到task_letvec[]表内。
  7.                 。注册了的处理函数群由tasklet_action函数执行。
  8.                 。可以为每个CPU分别注册(多处理器的章节将再度说明)
  9.         。HI_SOFTIRQ
  10.                 。构造与TASKLET_SOFTIRQ相同。好像其考虑为优先于TASKLET_SOFTIRQ处理。当前只用于BH处理函数(后述)。
  11.         。NET_TX_SOFTIRQ
  12.                 。用于TCP/IP协议栈的数据发送处理
  13.         。NET_RX_SOFTIRQ
  14.                 。用于TCP/IP协议栈的数据接收处理
复制代码

       
。函数说明


  1.     下面将说明上面涉及到的软中断处理操作函数。
  2.         。open_softirq()
  3.                 。将指定的处理函数注册到指定的软中断级别。(注册处为softirq_action[]表)
  4.         。__cpu_raise_softirq()
  5.                 。产生所指定的级别的软中断。
  6.         。do_softirq()
  7.                 。执行注册的软中断处理函数。
  8.                 。在系统调用出口,中断处理函数出口,异常处理函数出口,进程切换时,如果有软中断请求的话则会被呼叫。
  9.         。softirq_active()
  10.                 。检查是否有软件中断函数调用请求。
  11.         。tasklet_schedule(),tasklet_hi_schedule()
  12.                 。分别在TASKLET_SOFTIRQ级,HI_SOFTIRQ级注册处理函数。注册处理函数的时候,注册方负责准备各个tasklet_struct构造体,并设定处理函数的情报。
  13.                 。tasklet_struct结构体将排队注册入tasklet_vec[]表,tasklet_hi_vec[]表。
  14.         。tasklet_action(),tasklet_hi_action()
  15.                 。呼叫注册于TASKLET_SOFTIRQ级,HI_SOFTIRQ级的处理函数群
  16.                 。呼叫排队等候于tasklet_vec[]表,tasklet_hi_vec[]表的tasklet_struct结构体内的处理函数。
复制代码


                       
。BH处理函数



  1.     bottom half handler的简写。BH处理函数注册表bh_base[]为固定长,最多可注册32个处理函数。这些BH处理函数将被上边说的新型的软中断处理函数所呼叫。与软件中断处理函数不同的是,即使是在多处理器系统中,同时只能有一个CPU执行BH处理函数。这虽然简化了互斥处理,但对性能不利。
  2.     虽然当前兼容版本2.2为止的BH处理函数,但可能将会被慢慢淘汰。

复制代码


。函数说明


  1.     下面说明上面涉及的BH处理函数的操作函数。
  2.         。init_bh(BH_NO,bh_hdr)
  3.                 。将函数bh_hdr注册入BH处理函数注册表bh_base[]里的第BH_NO个位置。
  4.         。remove_bh(BH_NO)
  5.                 。删除注册于第BH_NO个位置的BH处理函数。
  6.         。mark_bh(BH_NO)
  7.                 。发送BH_H位的BH处理函数的呼叫请求。
  8.                 。为了将执行指定处理函数的函数(bh_action函数)作为软件中断处理函数执行,tasklet_schedule函数将其注册。注册时需要所需要的tasklet_struct构造体作为bh_task_vec[]静态分配。
  9.         。bh_action()
  10.                 。执行BH处理函数。由tasklet_action函数调用,为了与先前版本兼容,在BH处理函数执行过程中为禁止其他BH处理函数运行而设置禁止标识(仅在SMP时有意义)。
复制代码


                       
。任务队列


  1.     Bh处理函数只有32种,并且基本上都以被系统保留,因此引入了任务队列的概念。相当于可无限制注册的BH处理函数。扩充为可在一个BH处理函数的项内注册多数个处理。(第TQUEUE_BH个和IMMEDIATE_BH个项)。
  2.     如利用这个功能,则调用方需要准备注册到各个队列的延迟处理请求包。准备好后,剩下的就是用queue_task()函数注册这个处理。
  3.     queue_task()函数将其注册到目的队列。注册了的处理将在适当的时期由run_task_queue()执行。虽然注册到各个队列的延迟处理要求包需要调用方准备这一点很烦琐,但不用担心由于包数量不够而导致的注册失败。
  4.    任务队列有几个种类,更于用途不同选择不同的注册处。注册了的处理将由上图中显示的各个模块调用run_task_queue()函数来执行。
  5.         。tq_immediate中注册的处理将在无可用的内核处理的时候立即执行。实际上是使用BH处理函数来完成呼叫处理。
  6.         。tq_timer中注册的处理将根据时钟来确定执行时机。实际上使用BH处理函数来完成呼叫处理。
  7.         。tq_disk中注册的处理将在文件系统的适当时机被调用。可延迟对驱动程序的I/O请求,并改变I/O顺序达到最高效率。文件系统模块外无法利用。
  8.         。tq_context中注册的处理将只能由设定为tq_context执行专用的线程来呼叫。可能是作为tq_scheduler的替代品。

复制代码


               
。函数说明


  1.     以下是上面描述的任务队列的操作函数。
  2.         。queue_task(hdr,tskq)
  3.                 。将延迟处理函数hdr注册到tskq
  4.         。run_task_queue(tskq)
  5.                 。执行所有注册到tskq中的延迟处理处理函数。
复制代码

论坛徽章:
0
4 [报告]
发表于 2005-10-07 10:13 |只看该作者

Linux2.4内核内部分析报告(初稿4)[译文][进行中]

楼主可以给出原文吗?谢谢

论坛徽章:
0
5 [报告]
发表于 2005-10-07 10:21 |只看该作者

Linux2.4内核内部分析报告(初稿4)[译文][进行中]

[quote]原帖由 "hellhell"]楼主可以给出原文吗?谢谢[/quote 发表:


http://japan.linux.com/kernel/internal24/node1.shtml

原文在这里,是日文的。

顺便问个问题,handler中文叫什么,我里面全部翻译成处理函数,但是它并不一定是函数,很有问题。。。

论坛徽章:
1
荣誉会员
日期:2011-11-23 16:44:17
6 [报告]
发表于 2005-10-07 10:28 |只看该作者

Linux2.4内核内部分析报告(初稿4)[译文][进行中]

日文都这么厉害.佩服亚.

论坛徽章:
0
7 [报告]
发表于 2005-10-07 10:46 |只看该作者

Linux2.4内核内部分析报告(初稿4)[译文][进行中]

我也在怀疑是日文的......果然啊.......

论坛徽章:
0
8 [报告]
发表于 2005-10-07 12:06 |只看该作者

Linux2.4内核内部分析报告(初稿4)[译文][进行中]

[/code]
               
计时器(timer)

               
计时器控制

[code]
     Linux的计时器处理分为两部分。一个是硬件中断控制部分,另一个使用BH处理函数的部分。
    中断控制的计时器对被称为jiffies的系统起动开始经过时间的变量进行更新,及进行与当前进程相关的处理。此外的处理则全部作为延迟处理来实现。
    对当前进程的处理从版本2.4开始由BH处理函数处理改为由中断处理函数处理。
        。更新jiffies
        。对当前进程进行处理(update_process_times函数)
                。profiling
                。收集统计情报
                。再调度请求
        。请求起动处理计时器的函数(timer_bh函数)
        。请求起动任务队列tq_timer
    时钟处理函数(timer_bh函数)作为BH处理函数起动。这个函数做下面列举的几点处理。与中断处理函数不同的是,它的处理有可能被长时间延迟。在这个BH处理函数(timer_bh函数)起动前,中断处理函数(do_timer函数)有可能运行过数次,因此这种时候timer_bh函数需要将数个时钟单位的处理集中起来同时处理。
        。当前时刻(GMT时间)的更新(update_wall_time函数)
        。计算Load Average(calc_load函数)
        。管理及执行计时器列表(run_timer_list函数)
[/code]

               
计时器列表

[code]
    linux也和传统的UNIX一样,可以注册callback handler(将在注册后经过一段时间后被呼叫)。(用于TCP/IP的超时处理,再送信处理等)
    这部分设计得十分牢固,可指定expire时间使用add_timer函数在下表中注册handler。每次的时钟(clock)处理时都将参照这个表,如果有达到expire时间的handler则会呼叫它。由于注册入表的数据结构由呼叫方负责准备,所以不会溢出(overflow)。
    除此外,linux还准备了一个十分原始的计时器,但是现在几乎不被使用。
[/code]



函数说明

[code]
        。add_timer()
                。将handler注册到计时器列表。根据到expire时间为止的长短来选择timer_vec。
        。del_timer(),del_timer_sync()
                。从计时器列表里删除handler。在单处理器中del_timer_sync()的处理与del_timer()相同。而在多处理器时,如果指定的计时器正在工作,则会等待其完成后再进行删除处理,这一点和del_timer()不同。
        。mod_timer()
                。修改注册入计时器列表的handler的起动时间。
        。run_timer_list()
                。呼叫注册于计时器列表内到达expire时间的handler。每个时钟(clock)执行timer_vec_root的一个项(entry)。
                。每当timer_vec_root的一个项到达一个周期(256 tick)时,则将其上一层的timer_vec的计数(counter)加一,并在读取项后将其展开到timer_vec_root内。
                。第一个timer_vec的某个项达到一个周期(256x64 tick)的时候,则将其上一层(第二个)的timer_vec的计数也加一,读取一个项后将其展开到第一个timer_vec。
[/code]


其他与计时器相关的功能


内核内的时间等待

[code]
    综合使用上面所说的计时器列表和调度器,Linux可以在内核内等待一定的时间。
        。schedule_timeout(timeout)
                。放弃CPU timeout时间长。使用add_timer函数注册入计时器列表,保证timeout时间后呼叫下面说的process_timeout,然后呼叫调度器(schedule函数)。
                。为了实现nanosleep系统调用使用了这个功能。
        。process_timeout()
                。进行上面说的处理来唤醒经过了timeout时间的进程。所作的工作与wake_up_process函数相同。
        。sleep_on_timeout()
                。给sleep_on函数增加超时功能的函数。放弃CPU时使用schedule_timeout函数。
                。存在一个imterruptible_sleep_on函数以对应interruptible_sleep_on函数,
[/code]


setitimer系统调用

[code]
    为了实现settimer系统调用,每一个进程的task_struct内有一个成员real_timer表,用于注册到各个计时器列表。发行了setitimer系统调用的话,则会使用add_timer函数将这个表注册入上面说的计时器列表。经过了指定时间的话则会呼叫注册了的handler向进程发送信号(SIGALRM,SIGVTLALRM,SIGPROF)。信号发送完成后则再次使用add_timer函数再次注册入计时器列表。
        。do_setitimer()
                。如果setitimer指定为实时的话,则将当前进程的real_timer注册到计时器列表。
                。如果setitimer指定为相对时间的话,则只设定超时时间而不使用计时器列表。超时时间由在时钟处理中polling来实现。
        。it_real_fn()
                。setitimer指定为实时的话,超时时被呼叫的函数
                。生成SIGALRM信号后再次使用add_timer注册。
        。do_it_virt()
                。在时钟处理函数(timer_bh函数)处理延长时被呼叫
                。计算当前进程的运行时间,如果达到超时时间的话则生成SIGVTLALRM信号,之后再次设定超时时间。
        。do_it_prof()
                。除其生成的信号为SIGPROF外,其余与do_it_virt()函数相同。
[/code]

论坛徽章:
0
9 [报告]
发表于 2005-10-07 12:17 |只看该作者

Linux2.4内核内部分析报告(初稿4)[译文][进行中]

原帖由 "hellhell"]我也在怀疑是日文的......果然啊.......[/quote 发表:

噢?我还以为你是对文档本身有兴趣呢...
[quote]原帖由 "THEBEST" 发表:

日文都这么厉害.佩服亚.

该佩服的是计算机利害的,全世界1亿多会日语的,计算机利害的才多少阿


这个今天就翻这么多了,消化不完,准备把感兴趣的翻完后加深点理解,然后再改错加note。。。

本来看它那么长都有点想放弃翻译了,后来想想人家写都能写出来翻翻怎么能嫌烦呢。。。

还有,好像大家都没兴趣阿。

论坛徽章:
0
10 [报告]
发表于 2005-10-07 13:35 |只看该作者

Linux2.4内核内部分析报告(初稿4)[译文][进行中]

timer还是翻译成定时器比较符合习惯些,楼主精神可嘉,再多发一些,给你加个精华如何?
P.S. 我十一也没闲着,翻译了Rusty Russell的《Unreliable Guide to kernel Locking》,2.6版的,明天发在linuxforum.net上,有感兴趣的朋友可以去看看。
您需要登录后才可以回帖 登录 | 注册

本版积分规则 发表回复

  

北京盛拓优讯信息技术有限公司. 版权所有 京ICP备16024965号-6 北京市公安局海淀分局网监中心备案编号:11010802020122 niuxiaotong@pcpop.com 17352615567
未成年举报专区
中国互联网协会会员  联系我们:huangweiwei@itpub.net
感谢所有关心和支持过ChinaUnix的朋友们 转载本站内容请注明原作者名及出处

清除 Cookies - ChinaUnix - Archiver - WAP - TOP