




已阅读5页,还剩4页未读, 继续免费阅读
版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领
文档简介
LINUX中断与系统调用实现机制及fork实例分析1.Linux系统调用概述 1.1 系统调用的作用 系统调用(system_call)是操作系统中必不可少的一个组成部分,系统调用命令是操作系统为满足用户所需的功能和保证程序的正常运转事先编制好的具有特定功能的例行子程序。在高级语言如C中,常以函数的形式出现。它们使得编程人员不需要太多了解系统就能完成复杂的编程。例如,在程序中安排一条创建进程的系统调用,则OS便会为之创建一个新的进程。它一般运行在核心态;可通过中断进入,返回时通常需要重新调度。 系统调用是用户程序和内核交互的接口。如果没有系统调用,那么应用程序就失去了内核的支持。 提到内核态及用户态,就顺便介绍下LINUX的内核保护机制。 1.2 内核保护机制 Linux系统在CPU的保护模式下提供了四个特权级别,目前内核都只用到了其中的两个特权级别,分别为“特权级0”和“特权级3”,级别0也就是通常所讲的内核模式,级别3即用户模式。划分这两个级别主要是对系统提供保护。内核模式可以执行一些特权指令和进入用户模式,而用户模式则不能。 内核模式与用户模式分别使用各自的堆栈,当发生模式切换的时候同时要进行堆栈的切换。每个进程都有自己的地址空间(也称为进程空间),进程的地址空间也分为两部分:用户空间和系统空间,在用户模式下只能访问进程的用户空间,在内核模式下则可以访问进程的全部地址空间,这个地址空间里的地址是一个逻辑地址,通过系统段面式的管理机制,访问的实际内存要做二级地址转换,即:逻辑地址 - 线性地址 - 物理地址。 系统调用对于内核来说就相当于函数,关键问题就是从用户模式到内核模式的转换、堆栈的切换以及参数的传递。下文将详细分析linux系统调用的流程,并且以产生进程的系统调用(fork)为例加以了说明。2. Linux系统调用实现机制 2.1 系统调用与中断机制之间的关系 系统调用本身也是由若干条指令构成的过程。但它与一般的过程不一样,主要区别是:系统调用是运行在系统态(kernel mode),而一般过程是运行在用户态(user mode),它必须通过中断进入OS系统态(在linux中,是通过中断int 0x80h进入OS态),再由该中断的处理函数来具体决定对不同的系统调用的具体处理。即linux系统是通过中断处理来实现系统调用的。 在Intel X86芯片上运行的Linux中断机制简述如下: 1.在Linux中中断可以分为两类: 软中断(也可称其为异常)和硬中断 2.Linux中由中断向量表来管理中断. 每个中断对应一个中断号, 以该中断号作为下标在中断向量表中对应一长为4个字节的中断向量用于表示该中断服务函数的入口地址。 Linux中共可有256类中断, 则对应着1KB长的中断向量表。表的结构大体如下:中断号中断类型0除零中断1调试中断2NMI 不可屏蔽中断3Break Point断点中断4由INTO引发的溢出中断516其他各种已设定的中断1731保留的中断32开始各种可屏蔽中断0x80用于系统调用的中断255结束中断表尾 从上表可以了解到, 用于系统调用的0x80号中断属于可屏蔽中断, 其优先级在各种中断中为较低的。所有的系统调用都将通过该中断把控制权交给系统, 进入到核心态中。 2.2 相关的数据结构与函数 2.2.1 相关的数据结构 linux里面的每个系统调用是靠一些宏,一张系统调用表,一个系统调用入口来完成的。 (1)系统调用表 在文件arch/i386/entry.S中定义了系统调用表(sys_call_table),该表保存了Linux的所有基于Intel x86系列体系结构的计算机的所有系统调用入口地址,其中每项都被说明成 long型。 格式如下:.dataENTRY(sys_call_table).long SYMBOL_NAME(sys_setup) /* 0 */.long SYMBOL_NAME(sys_exit).long SYMBOL_NAME(sys_fork).long SYMBOL_NAME(sys_nanosleep) .long SYMBOL_NAME(sys_mremap).long 0,0.long SYMBOL_NAME(sys_vm86).space (NR_syscalls-166)*4/*此段罗列了166个系统调用,这句表示可以被用户用来自由添加系统的空间。NR_syscalls是在include/linux/sys.h文件中定义的宏,其值为256,表示x86微机上最多可容纳的系统调用个数*/ (2) 宏宏就是_syscallN(type,name,x.),N是系统调用所需的参数数目,type是返回类型,name即面向用户的系统调用函数名,x.是调用参数。这些宏定义于includeasmUnistd.h,这就是为什么在程序中要包含这个头文件的原因。该文件中还以_NR_name的形式定义了164个常数,这些常数就是系统调用函数name的函数指针在系统调用表中的偏移量。与系统调用表对应,在unistd.h中为每一种系统调用分配了一个唯一的编号(syscall number),相应于系统调用在sys_call_table中的偏移量。如:#define _NR_setup 0#define _NR_exit 1 #define _NR_fork 2 #define _NR_read 3 以系统调用号_NR_name作为下标,找出系统调用表sys_call_table (arch/i386/kernel/entry.S)中对应表项的内容,正好就是该系统调用的响应函数sys_name的入口地址。 2.2.2 相关函数 (1)系统调用入口函数系统调用入口函数定义于entry.s: ENTRY(system_call) pushl %eax # save orig_eax SAVE_ALL#ifdef _SMP_ ENTER_KERNEL#endif movl $-ENOSYS,EAX(%esp) cmpl $(NR_syscalls),%eax jae ret_from_sys_call movl SYMBOL_NAME(sys_call_table)(,%eax,4),%eax testl %eax,%eax je ret_from_sys_call#ifdef _SMP_ GET_PROCESSOR_OFFSET(%edx) movl SYMBOL_NAME(current_set)(,%edx),%ebx#else movl SYMBOL_NAME(current_set),%ebx#endif andl $CF_MASK,EFLAGS(%esp) movl %db6,%edx movl %edx,dbgreg6(%ebx) testb $0x20,flags(%ebx) jne 1f call *%eax movl %eax,EAX(%esp) jmp ret_from_sys_call 这段代码先保存所有的寄存器值,然后检查调用号(_NR_name)是否合法(在系统调用表中查找),找到正确的函数指针后,就调用该函数(即你真正希望内核帮你运行的函数)。运行返回后,将调用ret_from_sys_call,这里就是著名的进程调度时机之一。 当在程序代码中用到系统调用时,编译器会将上面提到的宏展开,展开后的代码实际上是将系统调用号放入ax后移用int 0x80使处理器转向系统调用入口,然后查找系统调用表,进而由内核调用真正的功能函数。 自己添加过系统调用的人可能知道,要在程序中使用自己的系统调用,必须显示地应用宏_syscallN。 (2)中断入口system_call()(/arch/i386/entry.s中) system_call是所有系统调用的入口。它的功能是:保存所有寄存器,检验是否是合法的系统调用,根据_sys_call_table中的偏移量把控制权转给真正的系统调用代码,系统调用完毕后调用_ret_from_sys_call(),返回到用户空间。 2.3系统调用的流程 实现系统调用的相关文件包括system_call.s、fork.c、signal.c、sys.c、和exit.c文件。system_call.s的流程如下图所示,它还给出了两个系统功能的底层接口 sys_execve和sys_fork,另外列出了处理过程类似的协处理器出错(131行)、设备不存在(148行)、时钟中断(176行)、硬盘中断(221行)和软盘中断(252行)。进入_system_call的公共入口(80)调用号是否超过了上限(81)行)Ybad_sys_call:置eax=-1(72)保存原段寄存器值,从用户数据段切换到核心态数(83起)取出该调用在系统调用表中的对应项地址(94)reschedule:调用schedule() (76)任务状态就绪否(97) N时间片用完否(99) Y 是初始任务?(102)YY是内核任务?(105)是用户堆栈?(107)NIret 退出中断根据进程信号位图取进程的最小信号量,调用do_signal() (119) 3. 重点实例分析-fork系统调用 3.1 系统调用fork简介 在linux中,只有一个函数可以创建子进程:fork fork()是一个十分重要的系统调用,当一个进程在它的代码中发出fork()的调用后,系统将从该进程继承出一个与它几乎完全相同的子进程该子进程除特定的进程号不同外,将具有与其父进程大致相同的代码段,用户数据段和核心数据态这也就是说,最初时子进程将与父进程运行完全相同的代码,且它将拥有与父进程完全相同的系统资源(实际和有效的uid 和gid)若系统要让子进程运行别的程序,可以调用execve()将一个特定的程序代码装入子进程的地址空间中而这以后父进程可以与它并行执行,也可用系统调用wait()来等待子进程的退出 最明显的例子是Linux初始化时将使用fork()来派生一系列的子进程,调用顺序如下:号进程号进程tty终端进程Login进程用户进程 该调用链中,后一个进程都是由前一个用fork()生成,再用execve()赋予它特定的功能注:init进程作为该进程所有子进程的父进程。 fork函数被调用一次,但返回两次。两次返回的区别是子进程的返回值是0,而父进程的返回值则是新子进程的进程I D。将子进程I D返回给父进程的理由是:因为一个进程的子进程可以多于一个,所以没有一个函数使一个进程可以获得其所有子进程的进程I D。f o r k使子进程得到返回值0的理由是:一个进程只会有一个父进程,所以子进程总是可以调用g e t p p i d以获得其父进程的进程I D (进程ID 0总是由交换进程使用,所以一个子进程的进程I D不可能为0 )。子进程和父进程共享很多资源,除了打开文件之外,很多父进程的其他性质也由子进程继承: 实际用户I D、实际组I D、有效用户I D、有效组I D。 添加组I D。 进程组I D。 对话期I D。 控制终端。 设置-用户- I D标志和设置-组- I D标志。 当前工作目录。 根目录。 文件方式创建屏蔽字。 信号屏蔽和排列。 对任一打开文件描述符的在执行时关闭标志。 环境。 连接的共享存储段。 资源限制。 父、子进程之间的区别是: fork的返回值。 进程I D。 不同的父进程I D。 子进程的t m s _ u t i m e , t m s _ s t i m e , t m s _ c u t i m e以及t m s _ u s t i m e设置为0。 父进程设置的锁,子进程不继承。 子进程的未决告警被清除。 子进程的未决信号集设置为空集。 使f o r k失败的两个主要原因是:( a )系统中已经有了太多的进程(通常意味着某 个方面出了问题),或者( b )该实际用户I D的进程总数超过了系统限制。回忆表2 - 7,其中C H I L D _ M A X规定了每个实际用户I D在任一时刻可具有的最大进程数。 f o r k有两种用法:(1) 一个父进程希望复制自己,使父、子进程同时执行不同的代码段。这在网络服务进程中是常见的父进程等待委托者的服务请求。当这种请求到达时,父进程调用f o r k,使子进程处理此请求。父进程则继续等待下一个服务请求。(2) 一个进程要执行一个不同的程序。这对s h e l l是常见的情况。在这种情况下,子进程在从f o r k返回后立即调用e x e c。 3.2 系统调用fork的设置如果调用成功,fork系统调用对父进程返回新生成的子进程的进程标识号pid,对子进程返回0;否则,将出错原因存入error变量,并向父进程返回-1。产生的出错原因有:1 ENOMEM: fork为自己的存在申请内存空间失败。2 EAGAIN: fork为子进程的PCB的数据项分配内存空间失败。 fork与clone类似,只是clone是创建一个与父进程完全相同的新进程,两者的pid也相同。在include/asm-i386/unistd.h中进行系统调用的设置(包括fork);该系统调用fork的设置使用的宏应为:_syscall0(int,fork),因为fork()是不带参数的,且返回类型为int。在include/asm-i386/unistd.h中,fork的内联宏语句为: static inline _syscall0(int,fork)。这样,在调用fork时,系统将调用宏指令_syscall0(int,fork),进而调用0x80号中断,此时寄存器eax中的值为_NR_fork(=2),作为参数传给int $0x80。调用中断int $0x80后,System_call在保存所有寄存器,检验是否是合法的系统调用后,用eax中的值(_NR_fork)乘4得到系统调用表(sys_call_table)中的偏移,找到入口:.long SYMBOL_NAME(sys_fork)接着转向函数sys_fork()(arch/i386/kernel/process.c中):asmlinkage int sys_fork(struct pt_regs regs)return do_fork(SIGCHLD, regs.esp, ®s); SIGCHLD (signal.h中)是一种信号类型,作为do_fork的第一个参数clone_flags指明do_fork()函数应创建一子进程: #define SIGCHLD17sys_fork()将类型为struct pt_regs(寄存器帧)的regs的地址作为参数传递给do_fork(),并且通过寄存器:regs.esp传递了栈顶指针。由于系统调用是通过寄存器传递参数的,且system_call 已将所有的CPU寄存器保存在堆栈中,根据2.2.2的讨论,只要regs首址为当 前堆栈的栈顶指针,就可以通过regs取压入堆栈的各寄存器值(参数值)。 实际上,fork系统调用最终是由do_fork()函数完成的(clone也是借助do_fork完成的,不同之处在于传入do_fork()的参数不同)。do_fork()在task数组中找到空闲位置,继承父进程现有资源,初始化进程时钟、信号、时间等数据。函数do_fork()的流程: 为新进程申请内存空间 申请系统堆栈将新进程的 PCB添入PCB-SET中拷贝当前进程内容 (copy_files,copy_fs,copy_sighand,copy_mm,copy_thread) 进行数据成员初始化 返回新进程的pid以上只是粗略的流程,关于各步的出错处理,参见详细代码。4. 其它文件及函数 4.1 exit.c 正与 fork 系统调用创建一个子进程相反, exit 的目的是为了撤销一个进程, 像上面系统调用表中提到的那样, 通过调用 sys_exit( ) 函数终止一个进程的运行.处理期间, 要保存当前帐号的各种信息,逐步退出占用的资源, 还要处理好与之有关的其他进程. 主要包括进程释放、会话终止和程序退出处理函数以及杀死进程、终止进程、挂起进程等系统调用函数。还包括进程信号发送函数和通知父进程子进程终止的函数。 进程释放release()函数(19行)根据指定的任务数据结构指针*p,在任务数组中寻找指定任务,找到了则置空该任务项并释放相关内存页并进行重新调度,若任务不存在则僵死(panic()函数是当系统发现无法继续运行下去的故障时将调用它,显示错误信息并使系统进入死循环)。 会话终止kill_session()函数(46行)首先将指针指向任务数组最末端,遍历任务数组并向与当前进程处于同一会话期的进程递送挂断信号。 程序退出处理函数do_exit()(102行)在系统调用的中断处理程序中被调用,先释放当前进程代码段和数据段所占的内存页,将当前进程的子进程的父进程改为进程1,若该子进程已经僵死,则向进程1(即其父进程)发进子进程终止信号SIGCHLD。再关闭当前进程打开着的所有文件,释放使用的终端设备、协处理器设备,若当前进程是进程组的领头进程,则终止所有相关进程,后把当前进程置为僵死状态,设置退出码,并向其父进程发送子进程终止信号,最后重新调度任务。 系统调用sys_kill()(60行)根据pid(进程标识号)的数值不同可以向任何进程或进程组发送任何指定信号。如果进程id为0,从最后一个进程槽开始遍历进程数组,表示把信号发送给与当前进程同组的所有进程中去;如果进程id大于0,从最后一个进程槽开始遍历数组,如果进程id为-1,表示向所有进程发送该信号,除了进程0. 如果进程id小于0且不等于0,则将信号发送到同pid绝对值相等的进程组的所有进程。 系统调用sys_exit()(137行)调用do_exit()退出进程。do_exit()函数直接使进程停止运行,清除其使用的内存空间,并销毁其在内核中的各种数据结构。 系统调用sys_waitpid()(142行)挂起当前进程,直到pid指定的子进程退出(终止)或收到要求终止该进程的信号或需要调用一个信号句柄。若pid所指的子进程已退出(或僵死)则本调用将立刻返回。释放子进程使用的所有资源。 进程信号发送函数send_sig()(35行)负责向指定任务发送信号。先判断给定进程指针是否为空,信号是否越界。后更新指定进程的信号位图:1. 用户是超级用户;2. 当前进程的有效用户id和指定进程的有效用户id相同;3. 如果有特殊特权级,priv != 0。 通知父进程子进程终止函数tell_father()(83行)如果pid有效,即大于0,遍历任务数组,向与pid相同的进程递送SIGCHILD信号,如果没找到,则释放当前进程资源。 另:与do_exit()函数的区别,sys_exit()函 数在do_exit基础上作了一些包装,在执行退出之前加了若干道工序,而并不就只是直接停止运行进程并清除内存销毁数据结构。假如有一些数据,我们认为已经写入了文件,实际上因为没有满足特定的条件,它们还只是保存在缓冲区内,这时我们用do_exit()函数直接将进程关闭,缓冲区中的数据就会丢失,而使用exit()函数就能保证数据的完整性。 4.2 sys.c 此程序含有很多系统调用功能的实现函数。其中含
温馨提示
- 1. 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。图纸软件为CAD,CAXA,PROE,UG,SolidWorks等.压缩文件请下载最新的WinRAR软件解压。
- 2. 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
- 3. 本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
- 4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
- 5. 人人文库网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
- 6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
- 7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。
最新文档
- 聚焦2025:氢能重卡在可持续发展战略中的应用与技术挑战报告
- 年满六十周岁的自然人离婚起诉书范本
- 考研无水印资料群(3篇)
- 安全培训防烫伤考试题和及答案解析
- 2025年公务员考试公共基础知识试题库与答案
- 保姆岗位招聘笔试题与参考答案(某大型国企)2025年
- 2025年农学专业综合知识考试试卷及答案
- 2025年高校辅导员职业素质考试试卷及答案
- 2025年海洋能发电与海水淡化联合系统在海洋高科技产业中的应用前景报告
- 2025年海洋能发电项目政策法规环境分析报告
- T-SZEIA 001-2024 温室气体产品碳足迹量化方法与要求 变电站电气设备
- 2025年湖南省安全员-B证考试题库及答案
- 北师大版六年级下册数学全册同步分层作业设计含答案解析
- 简易钢结构雨棚施工承包合同范本
- 苏州市前期物业管理委托合同范本
- 2022年冀教版七年级上册数学第一次月考试卷
- 《气管支架临床应用》课件
- 导数的应用-函数的零点问题(5题型分类)-2025年高考数学一轮复习(解析版)
- 8·12天津滨海新区爆炸事故调查报告分析及反思
- 2024新指南:中国阿尔茨海默病早期预防指南解读课件
- 江苏省南京市联合体2024-2025学年八年级上学期期中考试语文试题含答案
评论
0/150
提交评论