arm Linux 系统调用分析
Linux系统调用分析报告
那么又是什么呢?
是对应的不同的系统调用的号码,权且叫它系统调用号吧.定义在中,如下:
* , *
....
将系统调用号放入中,留于在()之中用(上面提到过的).
要利用系统调用要把包含进去.
最后的收尾工作:
由来完成,如下.
:
$()
:()
()
$(), ()
():
$()
$()()?
:
$()
$'
()
$()
();
....
();
();
(<)
();
();
....
}
与都是宏.它们可以在之中找到如下.
() \
([])
() \
([])
() \
([])
() \
()
其中的也在同一文件中,如下.
() \
(" \\" \
" \\" \
" \\" \
" " \
:"" (*(( *) ())), \
"" (*(( *) ())) \
系统调用分析报告
计算机系沈华品
学号
摘要:
系统调用()简单的看作是操作系统提供的一种编程接口,它提供给编程人员
许多功能调用.(在高级语言如中,常以函数的形式出现).这些功能要自己实现起来是比较麻烦的.有了操作系统提供系统调用,使得编程人员不需要太多了解系统就能完成复杂的编程.例如,在程序中安排一条创建进程的系统调用,则便会为之创建一个新的进程.
Linux系统调用过程分析
Linux系统调用分析计算机962班周从余一.与系统调用有关的一些基本知识1.系统调用的定义在OS的核心中都设置了一组用于实现各种系统共能的子程序,并将它们提供给用户程序调用.每当用户在程序中需要OS提供某种服务时,便可利用一条系统调用命令,去调用所需的系统过程.所以说系统调用在本质上是一种过程调用.系统调用是进程和操作系统之间的接口,这些调用一般就是一些汇编指令集,在Linux系统中这些调用是用C语言和汇编编写的。
用户只有通过这些系统调用才能使用操作系统提供的一些功能.2.系统调用与过程调用的区别过程调用调用的是用户程序,它运行在用户态;其被调用过程是系统过程,运行在系统态下.系统调用是通过软中断机制进入OS核心,经过核心分析后,才能转向响应的命令处理程序.系统调用返回时通常需要重新调度.系统调用允许嵌套调用.3.中断与异常中断(interrupt)是由外部事件的,可以随时随地发生(包括在执行程序时)所以用来响应硬件信号。
在80386中,又把中断分为两种:可屏蔽中断(Miscible Interrupt)MI不可屏蔽中断(NonMaskable Interrupt)NMI异常(exception)是响应某些系统错误引起的,也可以是响应某些可以在程序中执行的特殊机器指令引起的. 异常也分为两种:处理器异常,(指令内部异常如overflow 等)编程(调试)异常(debugger)每一个异常或中断都有一个唯一的标识符,在linux文献中被称为向量。
指令内部异常和NMI(不可屏蔽中断)的中断向量的范围从0—31。
32-255的任何向量都可以用做可屏蔽中断编程(调试)异常至于可屏蔽中断则取决于该系统的硬件配置。
外部中断控制器(External interruptcontroler)在中断响应周期(interrtupt acknowledge cycle)把中断向量放到总线上。
中断和异常的优先级:最高:除调试错误以外的所有错误最低: INTR中断。
Linux系统调用fork分析报告
Linux系统调用fork分析报告计算机系96级1班郑杰9615103系统调用是操作系统和用户之间的界面,它由系统核心设定,为用户提供各种系统服务。
用户以软中断的方式发出系统调用,系统核心响应以执行对应的系统调用服务程序。
Linux 处理系统调用的流程是这样的:先通过int $0x80中断进入系统调用入口,根据中断参数知道用户需要的系统调用在系统调用表(sys_call_table)中的偏移量,从而找到了系统调用服务程序的入口地址,进而去执行该程序。
本报告分两部分:第一部分大致分析一下系统调用的流程,第二部分分析其中用于创建新进程的重要的系统调用fork的工作过程。
重点放在第二部分。
一.系统调用简述我们可以这样理解:所有系统调用合在一起是其实是一种中断,就是int $0x80,系统初始化的时候,int $0x80和其它中断向量一样,中断服务程序入口地址被存入中断向量描述表中,其中第0x80项保存的即系统调用的入口地址。
各种trap入口都是在文件”\Arch\I386\Kernel\Traps.c”中的trap_init()函数设置的,其中0x80入口由语句 set_system_gate(0x80,&system_call);完成。
set_system_gate()是一个宏,在”Include\Asm-i386\System.h”中有它的宏展开:#define set_system_gate(n,addr) \_set_gate(&idt[n],15,3,addr)其中“_set_gate()”也是在该文件中定义的宏:#define _set_gate(gate_addr,type,dpl,addr) \__asm__ __volatile__ (“movw %%dx,%%ax\n\t” \“movw %2,%%dx\n\t” \“movl %%eax,%0\n\t” \“movl %%edx,%1” \:”=m” (*((long *) (gate_addr))), \“=m” (*(1+(long *) (gate_addr))) \:”i” ((short) (0x8000+(dpl<<13)+(type<<8))), \“d” ((char *) (addr)),”a” (KERNEL_CS << 16) \:”ax”,”dx”这段宏的主要功能是使addr地址放入gate_addr所指向的内存单元中,那么语句set_system_gate(0x80,&system_call);的含义就很明显:把中断向量描述表的第0x80项置为&system_call。
Linux分析报告--系统调用
Jun 7 14:36:34 ubuntu kernel: [ 5511.463309]黄健40johawea 40,this is a system call
Jun 7 14:36:34 ubuntu kernel: [ 5511.463322] swapper000
Linux分析实验报告
填写时间:
课程名称
Linux分析
实验名称
系统调用
姓名
黄健
学号
2009221104210040
专业年级
计算机科学与技术一班
一、实验目的:1、加深对系统调用原理的理解。
2、深入了解系统调用的执行流程。
3、学会增加系统调用及向内核添加内核函数。
二、实验设备:Linux:Ubuntu10.10内核:2.6.35-32 3.0.24
Jun 7 14:36:34 ubuntu kernel: [ 5511.463397] cpuset1112
Jun 7 14:36:34 ubuntu kernel: [ 5511.463400]黄健40johawea 40,this is a system call
Jun 7 14:36:34 ubuntu kernel: [ 5511.463406] khelper1212
Jun 7 14:36:34 ubuntu kernel: [ 5511.463381]黄健40johawea 40,this is a system call
Jun 7 14:36:34 ubuntu kernel: [ 5511.463387] kworker/0:11012
Jun 7 14:36:34 ubuntu kernel: [ 5511.463391]黄健40johawea 40,this is a system call
linux arm上系统调用流程
linux arm上系统调用流程
在LinuxARM架构中,系统调用是用户空间程序与内核进行交互的重要方式之一。
其流程主要包括以下几个步骤:
1、用户程序通过系统调用号和参数向内核发起系统调用请求。
2、内核根据系统调用号判断调用的具体功能,并将参数从用户空间复制到内核空间。
3、内核执行指定的系统调用功能,并将执行结果返回给用户程序。
4、用户程序处理系统调用的执行结果。
在ARM架构中,系统调用是通过软中断(SWI)的方式实现的。
具体来说,用户程序会在执行到相应的系统调用指令时触发一个软中断,中断处理程序会根据系统调用号调用相应的内核函数来完成系统调用的处理。
在Linux内核中,系统调用函数通常被封装在一个系统调用表中,用户程序在发起系统调用请求时会通过系统调用号来索引相应的系统调用函数。
需要注意的是,在ARM架构中,系统调用的参数传递还有一种特殊的方式,即通过寄存器来传递参数,而不是通过堆栈。
具体来说,r0-r3寄存器用于传递前4个参数,r4-r11寄存器用于传递后续的参数,r7寄存器则用于传递系统调用号。
总之,对于ARM架构上的Linux系统而言,系统调用是实现用户程序与内核通信的重要方式之一,理解其调用流程和参数传递方式对于深入了解Linux系统的运作原理具有重要意义。
arm-linux-gccldobjcopyobjdump使用总结
arm-linux工具的功能如下:arm-linux-addr2line 把程序地址转换为文件名和行号。
在命令行中给它一个地址和一个可执行文件名,它就会使用这个可执行文件的调试信息指出在给出的地址上是哪个文件以及行号。
arm-linux-ar 建立、修改、提取归档文件。
归档文件是包含多个文件内容的一个大文件,其结构保证了可以恢复原始文件内容。
arm-linux-c++flit 连接器使用它来过滤 C++ 和 Java 符号,防止重载函数冲突。
arm-linux-gprof 显示程序调用段的各种数据。
arm-linux-ld 是连接器,它把一些目标和归档文件结合在一起,重定位数据,并连接符号引用。
通常,建立一个新编译程序的最后一步就是调用ld。
arm-linux-nm 列出目标文件中的符号。
arm-linux-objcopy 把一种目标文件中的内容复制到另一种类型的目标文件中。
arm-linux-objdump 显示一个或者更多目标文件的信息。
使用选项来控制其显示的信息,它所显示的信息通常只有编写编译工具的人才感兴趣。
arm-linux-ranlib 产生归档文件索引,并将其保存到这个归档文件中。
在索引中列出了归档文件各成员所定义的可重分配目标文件。
arm-linux-readelf 显示elf格式可执行文件的信息。
arm-linux-size 列出目标文件每一段的大小以及总体的大小。
默认情况下,对于每个目标文件或者一个归档文件中的每个模块只产生一行输出。
arm-linux-string 打印某个文件的可打印字符串,这些字符串最少4个字符长,也可以使用选项-n设置字符串的最小长度。
默认情况下,它只打印目标文件初始化和可加载段中的可打印字符;对于其他类型的文件它打印整个文件的可打印字符。
这个程序对于了解非文本文件的内容很有帮助。
arm-linux-strip 丢弃目标文件中的全部或者特定符号。
arm-linux-gcc -wall -O2 -c -o $@ $<-o 只激活预处理,编译,和汇编,也就是他只把程序做成obj文件-Wall 指定产生全部的警告信息-O2 编译器对程序提供的编译优化选项,在编译的时候使用该选项,可以使生成的执行文件的执行效率提高-c 表示只要求编译器进行编译,而不要进行链接,生成以源文件的文件名命名但把其后缀由 .c 或 .cc 变成 .o 的目标文件-S 只激活预处理和编译,就是指把文件编译成为汇编代码arm-linux-ld 直接指定代码段,数据段,BSS段的起始地址-Tbss ADDRESS Set address of .bss section-Tdata ADDRESS Set address of .data section-Ttext ADDRESS Set address of .text section示例:${CROSS}ld -Ttext=0x33000000 led.o -o led.elf使用连接脚本设置地址:arm-linux-ld -Tbeep.lds start.o beep.o -o beep.elf其中beep.lds 为连接脚本如下:arm-linux-objcopy被用来复制一个目标文件的内容到另一个文件中,可用于不同源文件的之间的格式转换示例:arm-linux-objcopy –o binary –S elf_file bin_file常用的选项:input-file , outflie输入和输出文件,如果没有outfile,则输出文件名为输入文件名2.-l bfdname或—input-target=bfdname用来指明源文件的格式,bfdname是BFD库中描述的标准格式名,如果没指明,则arm-linux-objcopy自己分析3.-O bfdname 输出的格式4.-F bfdname 同时指明源文件,目的文件的格式5.-R sectionname 从输出文件中删除掉所有名为sectionname的段6.-S 不从源文件中复制重定位信息和符号信息到目标文件中7.-g 不从源文件中复制调试符号到目标文件中arm-linux-objdump查看目标文件(.o文件)和库文件(.a文件)信息arm-linux-objdump -D -m arm beep.elf > beep.dis-D 显示文件中所有汇编信息-m machine指定反汇编目标文件时使用的架构,当待反汇编文件本身没有描述架构信息的时候(比如S-records),这个选项很有用。
Linux高性能网络编程之系统调用过程简析
Linux高性能网络编程之系统调用过程简析第一部分:基础A PI1、主机字节序和网络字节序我们都知道字节序分位大端和小端:•大端是高位字节在低地址,低位字节在高地址•小端是顺序字节存储,高位字节在高地址,低位字节在低地址既然机器存在字节序不一样,那么网络传输过程中必然涉及到发出去的数据流需要转换,所以发送端会将数据转换为大端模式发送,系统提供API实现主机字节序和网络字节序的转换。
#include < netinet/in.h >// 转换长整型unsigned long htonl(unsigned long int hostlong);unsigned long ntohl(unsigned long int netlong);// 转换短整型unsigned short htonl(unsigned short inthostshort);unsigned short ntohl(unsigned short int netshort);2、socket地址(1)socket地址包含两个部分,一个是什么协议,另一个是存储数据,如下:struct sock ad dr{sa_family_t sa_family; // 取值:PF_UNIX(UNIX本地协议簇),PF_INET(ipv4),PF_INET6(ipv6)char sa_data[14]; // 根据上面的协议簇存储数据(UNIX本地路径,ipv4端口和IP,ipv6端口和IP)};(2)各个协议簇专门的结构体// unix本地协议簇struct sockaddr_un{sa_family_t sin_family; // AF_UNIXchar sun_path[18];};// ipv4本地协议簇struct sockaddr_in{sa_family_t sin_family; // AF_INETu_int16_t sin_port;struct in_addr sin_addr;};// ipv6本地协议簇struct sockaddr_in6{sa_family_t sin_family; // AF_INET6u_int16_t sin6_port;u_int32_t sin6_flowinfo;...};3、socket创建socket,bind,listen,ac cept,connect,close和shutdown作为linux网络开发必备知识,大家应该都都耳熟能详了,所以我就简单介绍使用方式,重点介绍参数注意事项。
深入理解Linux系统调用
深入理解Linux系统调用大家好,我是小风哥。
在前两篇文章《为什么计算机需要操作系统》《系统调用与函数调用有什么区别》中我们了解了什么是系统调用、为什么需要系统调用、系统调用与函数调用有什么区别,那么在今天的文章中我们从理论来到现实,看看Linux中的系统调用是怎样实现的。
首先我们先来简单复习下之前讲解过的知识。
系统调用和普通的函数调用没有本质区别,普通的函数调用一般调用的是我们自己编写的函数或者其它库函数,而系统调用调用的则是内核中的函数,更学术一点的说法是这样的,所谓系统调用是指用户态程序请求操作系统提供的服务。
一提到服务,大家最先想到的一定是服务器,假设客户端是浏览器,浏览器发送http请求,服务器接收到请求后进行解析然后调用相应的hander,从本质上讲就是客户端触发了服务器端的某个函数的运行,这时我们说客户端请求了服务器端上的服务。
而系统调用与此类似,只不过用户态程序并不是通过http触发了操作系统中某个函数的运行,而是通过机器指令来触发的,因为用户态的App和操作系统运行在同一台计算机系统上,而客户端和服务器端运行在不同的计算机系统中(绝大部分情况下),因此客户端只能通过网络协议http来与服务器进行通信。
更通俗的说法就是所谓系统调用是指用户态的某个函数调用内核中的某个函数。
接下来我们用一段简单的hello world程序看下系统调用,这段程序需要运行在x86_64下:•.datamsg: .ascii 'Hello, world!\n' len = . - msg.text .global _start_start: movq $1, %rax movq $1, %rdi movq $msg, %rsi movq $len, %rdxsyscall movq $60, %rax xorq %rdi, %rdisyscall 使用以下命令编译:••$ gcc -c test.S$ ld -o test test.o然后执行:••./testHello, world!这段汇编代码成功的打印出了hello world,这段代码是什么意思呢?注意看.data这一段,这里说的是程序定义了哪些数据,.text段是说程序中包含了哪些执行,我们之前提到进程的内存布局时总是说数据段以及代码段,这里的数据段指的就是汇编中的.data段、代码段指的就是汇编中的.text段,现在你应该明白了吧。
基于ARM平台的Linux内核分析与移植研究
是 Ln x iu 支持的体系结构的简称 2 . 在 .3 6 2的 内核代码
中 已经 完 全 包 含 了对 S C 4 0 件 体 系 的支 持 Ln x 3 24 硬 iu
内核 主要 由 5个 子 系 统 组 成 : 程 调 度 、 进 内存 管 理 、 虚
拟文件 系统 、 网络接 口、 进程 间通信 。 iu Ln x内核代码非 常庞大 , 整体代码结构如 图 1 所示 。
3 编 译 内核
内 核 编 译 的方 式 与 引 导 程 序 移 植 大 体 相 同 .利 用
m k m g 命 令 即 可 进 行 编 译 。 当 编 译 完 成后 , 编 a ez ae l 把 译 生 成 的 映 像 z ae 过 VV 下 载 到 硬 件 平 台上 . l g通 m II 就
体 的研 究和 开 发 , 并对 内核 进 行 相 应 的修 改 和优 化 。通 过 配置 、 译 完成 整 个移 植 过 程 . 编 为
Ln x 内 核 移 植 提 供 借 鉴 。 iu
关 键 词 :Ln x 内核 ;¥ C2 4 A;内核 移 植 ;Neftr iu 3 40 tl ie
nt 而 ¥ C 4 0 理 器 包 含 了 MM i, 3 24 处 1 U模 块 , 以需 要 针 所 对 该 体 系结 构选 择 对 Ln x内核 对 MMU模 块 的 支 持 。 iu dvr: 目录 包 含 了 内 核 中 所 有 的 设 备 驱 动 程 i s该 e 序 。该 目录 占据 了 L u i x内核 的 大部 分 代码 , 常 庞大 。 n 非 是 进行 内核移 植 时需 要 重点 关 注 的 目录 . 如 L D显 示 例 C 驱 动程 序 、 摸屏 驱 动程 序 等 源代 码都 放 在该 目录下 。 触
arm-linux 启动代码分析——stage1 (1)
arm-linux 启动代码分析——stage1 (1)本文针对arm linux, 从kernel的第一条指令开始分析,一直分析到进入start_kernel()函数.我们当前以linux-2.6.19内核版本作为范例来分析,本文中所有的代码,前面都会加上行号以便于和源码进行对照.例:在文件init/main.c中:00478: asmlinkage void __init start_kernel(void)前面的"00478:" 表示478行,冒号后面的内容就是源码了.在分析代码的过程中,我们使用缩进来表示各个代码的调用层次.由于启动部分有一些代码是平台特定的,虽然大部分的平台所实现的功能都比较类似,但是为了更好的对code进行说明,对于平台相关的代码,我们选择at91(ARM926EJS)平台进行分析.另外,本文是以uncompressed kernel开始讲解的.对于内核解压缩部分的code,在arch/arm/boot/compressed中,本文不做讨论.一. 启动条件通常从系统上电到执行到linux kenel这部分的任务是由bootloader来完成.关于boot loader的内容,本文就不做过多介绍.这里只讨论进入到linux kernel的时候的一些限制条件,这一般是bootloader在最后跳转到kernel之前要完成的:1.CPU必须处于SVC(supervisor)模式,并且IRQ和FIQ中断都是禁止的;2. MMU(内存管理单元)必须是关闭的, 此时虚拟地址对物理地址;3. 数据cache(Data cache)必须是关闭的4. 指令cache(Instructioncache)可以是打开的,也可以是关闭的,这个没有强制要求;5. CPU 通用寄存器0 (r0)必须是0;6. CPU 通用寄存器1 (r1)必须是ARM Linux machinetype (关于machine type, 我们后面会有讲解)7. CPU 通用寄存器2 (r2) 必须是kernel parameterlist 的物理地址(parameter list 是由bootloader传递给kernel,用来描述设备信息属性的列表,详细内容可参考"Booting ARM Linux"文档).二. starting kernel首先,我们先对几个重要的宏进行说明(我们针对有MMU的情况): 宏 位置 默认值 说明KERNEL_RAM_ADDR arch/arm/kernel/head.S+26 0xc0008000 kernel在RAM中的的虚拟地址PAGE_OFFSET include/asm-arm/memeory.h+50 0xc0000000 内核空间的起始虚拟地址TEXT_OFFSET arch/arm/Makefile+137 0x00008000 内核相对于存储空间的偏移TEXTADDR arch/arm/kernel/head.S+49 0xc0008000 kernel的起始虚拟地址PHYS_OFFSET include/asm-arm/arch-xxx/memory.h 平台相关 RAM的起始物理地址内核的入口是stext,这是在arch/arm/kernel/vmlinux.lds.S中定义的:00011: ENTRY(stext)对于vmlinux.lds.S,这是ldscript文件,此文件的格式和汇编及C程序都不同,本文不对ldscript作过多的介绍,只对内核中用到的内容进行讲解,关于ld的详细内容可以参考这里的ENTRY(stext) 表示程序的入口是在符号stext.而符号stext是在arch/arm/kernel/head.S中定义的:下面我们将arm linuxboot的主要代码列出来进行一个概括的介绍,然后,我们会逐个的进行详细的讲解.在arch/arm/kernel/head.S中72 - 94 行,是armlinux boot的主代码:00072:ENTRY(stext) 00073: msr cpsr_c,#PSR_F_BIT | PSR_I_BIT | SVC_MODE @ ensure svc mode00074: @and irqsdisabled 00075: mrc p15, 0,r9, c0, c0 @ getprocessorid 00076:bl __lookup_processor_type @r5=procinfor9=cpuid 00077: movs r10,r5 @invalid processor (r5=0)?00078:beq __error_p @yes, error‘p’ 00079:bl __lookup_machine_type @r5=machinfo 00080: movs r8,r5 @invalid machine (r5=0)?00081:beq __error_a @yes, error‘a’ 00082:bl __create_page_tables 00083: 00084: 00091: ldr r13,__switch_data @address to jump to after00092: @mmu has beenenabled 00093: adr lr,__enable_mmu @return (PIC)address 00094: add pc,r10,#PROCINFO_INITFUNC 其中,73行是确保kernel运行在SVC模式下,并且IRQ和FIRQ中断已经关闭,这样做是很谨慎的.arm linux boot的主线可以概括为以下几个步骤:1. 确定processortype (75 - 78行)2. 确定machinetype (79 - 81行)3.创建页表 (82行) 4.调用平台特定的__cpu_flush函数 (在struct proc_info_list中) (94行) 5.开启mmu (93行)6. 切换数据 (91行)最终跳转到start_kernel (在__switch_data的结束的时候,调用了b start_kernel)下面,我们按照这个主线,逐步的分析Code.1. 确定processor type arch/arm/kernel/head.S中:00075: mrc p15, 0,r9, c0, c0 @ getprocessorid 00076:bl __lookup_processor_type @r5=procinfor9=cpuid 00077: movs r10,r5 @invalid processor (r5=0)?00078:beq __error_p @yes, error‘p’ 75行: 通过cp15协处理器的c0寄存器来获得processor id的指令.关于cp15的详细内容可参考相关的arm手册76行:跳转到__lookup_processor_type.在__lookup_processor_type中,会把processortype 存储在r5中77,78行: 判断r5中的processor type是否是0,如果是0,说明是无效的processortype,跳转到__error_p(出错)__lookup_processor_type 函数主要是根据从cpu中获得的processorid和系统中的proc_info进行匹配,将匹配到的proc_info_list的基地址存到r5中,0表示没有找到对应的processor type.下面我们分析__lookup_processor_type函数arch/arm/kernel/head-common.S中:00145:.type __lookup_processor_type,%function00146: __lookup_processor_type:00147: adr r3,3f00148: ldmda r3,{r5 - r7}00149: sub r3, r3,r7 @get offset between virt&phys00150: add r5, r5,r3 @convert virt addresses to00151: add r6, r6,r3 @physical address space00152: 1: ldmia r5,{r3,r4} @value, mask00153: and r4, r4,r9 @mask wanted bits00154: teq r3,r400155:beq 2f00156: add r5, r5,#PROC_INFO_SZ @sizeof(proc_info_list)00157: cmp r5,r600158:blo 1b00159: mov r5,#0 @unknown processor00160: 2: mov pc,lr00161:00162:00165: ENTRY(lookup_processor_type)00166: stmfd sp!,{r4 - r7, r9, lr}00167: mov r9,r000168:bl __lookup_processor_type00169: mov r0,r500170: ldmfd sp!,{r4 - r7, r9, pc}00171:00172:00176:.long __proc_info_begin00177:.long __proc_info_end00178:3: .long .00179:.long __arch_info_begin00180:.long __arch_info_end 145, 146行是函数定义147行: 取地址指令,这里的3f是向前symbol名称是3的位置,即第178行,将该地址存入r3. 这里需要注意的是,adr指令取址,获得的是基于pc的一个地址,要格外注意,这个地址是3f 处的"运行时地址",由于此时MMU还没有打开,也可以理解成物理地址(实地址).(详细内容可参考arm指令手册) 148行: 因为r3中的地址是178行的位置的地址,因而执行完后: r5存的是176行符号__proc_info_begin的地址; r6存的是177行符号__proc_info_end的地址; r7存的是3f处的地址. 这里需要注意链接地址和运行时地址的区别. r3存储的是运行时地址(物理地址),而r7中存储的是链接地址(虚拟地址). __proc_info_begin和__proc_info_end是在arch/arm/kernel/vmlinux.lds.S中: 00031: __proc_info_begin= .; 00032: *(.init) 00033: __proc_info_end= .; 这里是声明了两个变量:__proc_info_begin 和__proc_info_end,其中等号后面的"."是locationcounter(详细内容请参考) 这三行的意思是: __proc_info_begin 的位置上,放置所有文件中的".init"段的内容,然后紧接着是__proc_info_end 的位置. kernel 使用struct proc_info_list来描述processor type. 在include/asm-arm/procinfo.h 中: 00029: struct proc_info_list { 00030: unsignedint cpu_val; 00031: unsignedint cpu_mask; 00032: unsignedlong __cpu_mm_mmu_flags; 00033: unsignedlong __cpu_io_mmu_flags; 00034: unsignedlong __cpu_flush; 00035: constchar *arch_name; 00036: constchar *elf_name; 00037: unsignedint elf_hwcap; 00038: constchar *cpu_name; 00039: structprocessor *proc; 00040: structcpu_tlb_fns *tlb; 00041: structcpu_user_fns *user; 00042: structcpu_cache_fns *cache; 00043: }; 我们当前以at91为例,其processor是926的. 在arch/arm/mm/proc-arm926.S 中: 00464: .section ".init", #alloc,#execinstr 00465: 00466:.type __arm926_proc_info,#object 00467: __arm926_proc_info: 00468:.long 0x41069260 @ARM926EJ-S (v5TEJ) 00469:.long 0xff0ffff0 00470:.long PMD_TYPE_SECT | \ 00471: PMD_SECT_BUFFERABLE| \ 00472: PMD_SECT_CACHEABLE| \ 00473: PMD_BIT4 |\ 00474: PMD_SECT_AP_WRITE| \ 00475: PMD_SECT_AP_READ 00476:.long PMD_TYPE_SECT | \ 00477: PMD_BIT4 |\ 00478: PMD_SECT_AP_WRITE| \ 00479: PMD_SECT_AP_READ 00480:b __arm926_setup 00481:.long cpu_arch_name 00482:.long cpu_elf_name 00483:.long HWCAP_SWP|HWCAP_HALF|HWCAP_THUMB|HWCAP_FAST_MULT|HWCAP_VFP|HW CAP_EDSP|HWCAP_JA V A 00484:.long cpu_arm926_name 00485:.long arm926_processor_functions 00486:.long v4wbi_tlb_fns 00487:.long v4wb_user_fns 00488:.long arm926_cache_fns 00489:.size __arm926_proc_info,. - __arm926_proc_info 从464行,我们可以看到__arm926_proc_info 被放到了".init"段中. 对照struct proc_info_list,我们可以看到__cpu_flush的定义是在480行,即__arm926_setup.(我们将在"4.调用平台特定的__cpu_flush函数"一节中详细分析这部分的内容.) 从以上的内容我们可以看出: r5中的__proc_info_begin是proc_info_list的起始地址,r6中的__proc_info_end是proc_info_list的结束地址.149行:从上面的分析我们可以知道r3中存储的是3f处的物理地址,而r7存储的是3f处的虚拟地址,这一行是计算当前程序运行的物理地址和虚拟地址的差值,将其保存到r3中.150行: 将r5存储的虚拟地址(__proc_info_begin)转换成物理地址151行: 将r6存储的虚拟地址(__proc_info_end)转换成物理地址152行: 对照structproc_info_list,可以得知,这句是将当前proc_info的cpu_val和cpu_mask分别存r3,r4中153行: r9中存储了processorid(arch/arm/kernel/head.S中的75行),与r4的cpu_mask进行逻辑与操作,得到我们需要的值154行: 将153行中得到的值与r3中的cpu_val进行比较155行: 如果相等,说明我们找到了对应的processor type,跳到160行,返回156行: (如果不相等) , 将r5指向下一个proc_info,157行: 和r6比较,检查是否到了__proc_info_end.158行: 如果没有到__proc_info_end,表明还有proc_info配置,返回152行继续查找159行: 执行到这里,说明所有的proc_info都匹配过了,但是没有找到匹配的,将r5设置成0(unknownprocessor)160行: 返回2. 确定machine type arch/arm/kernel/head.S中:00079:bl __lookup_machine_type @r5=machinfo 00080: movs r8,r5 @invalid machine (r5=0)?00081:beq __error_a @yes, error ‘a’79行: 跳转到__lookup_machine_type函数,在__lookup_machine_type中,会把structmachine_desc的基地址(machine type)存储在r5中80,81行: 将r5中的machine_desc的基地址存储到r8中,并判断r5是否是0,如果是0,说明是无效的machinetype,跳转到__error_a(出错)__lookup_machine_type 函数下面我们分析__lookup_machine_type 函数: arch/arm/kernel/head-common.S中: 00176:.long __proc_info_begin00177:.long __proc_info_end00178:3: .long .00179:.long __arch_info_begin00180:.long __arch_info_end00181:00182: 00193:.type __lookup_machine_type,%function00194: __lookup_machine_type:00195: adr r3,3b00196: ldmia r3,{r4, r5, r6}00197: sub r3, r3,r4 @get offset between virt&phys00198: add r5, r5,r3 @convert virt addresses to00199: add r6, r6,r3 @physical address space00200: 1: ldr r3,[r5, #MACHINFO_TYPE] @ get machinetype00201: teq r3,r1 @matches loader number?00202:beq 2f @found00203: add r5, r5,#SIZEOF_MACHINE_DESC @ nextmachine_desc00204: cmp r5,r600205:blo 1b00206: mov r5,#0 @unknown machine00207: 2: mov pc,lr193, 194行: 函数声明195行: 取地址指令,这里的3b是向后symbol名称是3的位置,即第178行,将该地址存入r3. 和上面我们对__lookup_processor_type 函数的分析相同,r3中存放的是3b处物理地址.196行: r3是3b处的地址,因而执行完后: r4存的是3b处的地址 r5存的是__arch_info_begin 的地址 r6存的是__arch_info_end 的地址 __arch_info_begin 和__arch_info_end是在arch/arm/kernel/vmlinux.lds.S中: 00034: __arch_info_begin= .; 00035: *(.init) 00036: __arch_info_end= .; 这里是声明了两个变量:__arch_info_begin 和__arch_info_end,其中等号后面的"."是locationcounter(详细内容请参考) 这三行的意思是: __arch_info_begin 的位置上,放置所有文件中的".init"段的内容,然后紧接着是__arch_info_end 的位置. kernel 使用struct machine_desc 来描述machine type. 在include/asm-arm/mach/arch.h 中: 00017: struct machine_desc { 00018: 00022: unsignedint nr; 00023: unsignedint phys_io; 00024: unsignedint io_pg_offst; 00026: 00027: constchar *name; 00028: unsignedlong boot_params; 00029: 00030: unsignedint video_start; 00031: unsignedint video_end; 00032: 00033: unsignedint reserve_lp0:1; 00034: unsignedint reserve_lp1:1; 00035: unsignedint reserve_lp2:1; 00036: unsignedint soft_reboot:1; 00037:void (*fixup)(structmachine_desc *, 00038: struct tag *, char **, 00039: struct meminfo *); 00040:void (*map_io)(void); 00041:void (*init_irq)(void); 00042: structsys_timer *timer; 00043:void (*init_machine)(void); 00044: }; 00045: 00046: 00050: #defineMACHINE_START(_type,_name) \ 00051: static const struct machine_desc__mach_desc_##_type \ 00052:__attribute_used__ \ 00053:__attribute__((__section__(".init"))) ={ \ 00054:.nr =MACH_TYPE_##_type, \ 00055:.name =_name, 00056: 00057: #defineMACHINE_END \ 00058:}; 内核中,一般使用宏MACHINE_START来定义machine type. 对于at91, 在arch/arm/mach-at91rm9200/board-ek.c 中: 00137: MACHINE_START(AT91RM9200EK, "Atmel AT91RM9200-EK") 00138: 00139: .phys_io =AT91_BASE_SYS, 00140:.io_pg_offst =(AT91_VA_BASE_SYS >> 18)& 0xfffc, 00141:.boot_params =AT91_SDRAM_BASE + 0x100, 00142:.timer =&at91rm9200_timer, 00143:.map_io =ek_map_io, 00144: .init_irq =ek_init_irq, 00145:.init_machine =ek_board_init, 00146: MACHINE_END197行:r3中存储的是3b处的物理地址,而r4中存储的是3b处的虚拟地址,这里计算处物理地址和虚拟地址的差值,保存到r3中198行: 将r5存储的虚拟地址(__arch_info_begin)转换成物理地址 199行:将r6存储的虚拟地址(__arch_info_end)转换成物理地址 200行: MACHINFO_TYPE 在arch/arm/kernel/asm-offset.c 101行定义, 这里是取struct machine_desc中的nr(architecture number) 到r3中201行: 将r3中取到的machine type 和r1中的machine type(见前面的"启动条件")进行比较202行: 如果相同,说明找到了对应的machine type,跳转到207行的2f处,此时r5中存储了对应的structmachine_desc的基地址203行: (不相同), 取下一个machine_desc的地址204行: 和r6进行比较,检查是否到了__arch_info_end.205行: 如果不相同,说明还有machine_desc,返回200行继续查找.206行: 执行到这里,说明所有的machind_desc都查找完了,并且没有找到匹配的, 将r5设置成0(unknownmachine).207行: 返回3. 创建页表通过前面的两步,我们已经确定了processor type 和machine type.此时,一些特定寄存器的值如下所示:r8 = machineinfo (struct machine_desc的基地址)r9 = cpuid (通过cp15协处理器获得的cpu id)r10 =procinfo (struct proc_info_list的基地址)创建页表是通过函数__create_page_tables 来实现的.这里,我们使用的是arm的L1主页表,L1主页表也称为段页表(section page table)L1 主页表将4 GB 的地址空间分成若干个1 MB的段(section),因此L1页表包含4096个页表项(sectionentry). 每个页表项是32 bits(4 bytes)因而L1主页表占用4096 *4 = 16k的内存空间. 对于ARM926,其L1 section entry的格式为:(可参考arm926EJS TRM): 。
ARM+Linux的启动分析(zImage)
基于ARM的Linux的启动分析报告摘要:本文主要分析基于ARM的Linux-2.2.26内核启动过程。
将首先从/arch/arm/Makefile着手,介绍三种不同的启动方案,再剖析典型的压缩内核zImage启动方案的代码结构,最后将详细分析这种方案的启动过程,直到调用start_kernel()为止。
1、Linux内核的启动方案:由/arch/arm/Makefile的代码可以看出,主要有三种启动方案,分别是: echo '* zImage - Compressed kernel image (arch/$(ARCH)/boot/zImage)'echo ' Image - Uncompressed kernel image (arch/$(ARCH)/boot/Image)'echo ' bootpImage - Combined zImage and initial RAM disk'echo ' (supply initrd image via make variable INITRD=<path>)'Linux内核有两种映像:一种是非压缩内核,叫 Image,另一种是它的压缩版本,叫zImage。
根据内核映像的不同,Linux内核的启动在开始阶段也有所不同。
zImage是Image经过压缩形成的,所以它的大小比 Image小。
但为了能使用 zImage,必须在它的开头加上解压缩的代码,将 zImage解压缩之后才能执行,因此它的执行速度比Image要慢。
但考虑到嵌入式系统的存储空容量一般比较小,采用zImage可以占用较少的存储空间,因此牺牲一点性能上的代价也是值得的。
所以一般的嵌入式系统均采用压缩内核的方式(另外bootpImage是编译包含zImage和initrd的映像,可以通过make变量INITRD=<path>提供initrd映像)。
嵌入式Arm—Linux系统的网卡驱动程序的分析与实现
一一一 Ⅺ 一
甜
包 括 实 际 的设 备 ( 网卡 ) 虚 拟 设 备 ( 虚 拟 局 域 如 和 如
网) 。网络设 备可 分为 不 同的类 型 , 以太 网 和令 牌 环 如
作 。 因此 , 编写驱 动程序 , 要 首先 要对设 备具 有准确 的
a a y i g h Li u n t r d i e s r c u e e e r h n a d e l i g h wo k n p i cp e n k y n l z n t e n x e wo k rv r t u t r ,r s a c i g n r a i n t e z r i g rn i l a d e t c n lge f e h o o is o Ar Li u e wo k d v c m a i g o t e e a mp r a t p r s o e e a n t r rv r m- n x n t r e ie, k n u s v r l i o t n a t f g n r l e wo k d i e s r c u e, i h i c u e t e d vc e it a i n,I i a ii g e c e i e t u t r wh c n l d h e ie r g s r to n t l n a h d v c ,mo u e u l a i g,t ed vc t o s i z d l n o d n h e ie me h d ( p n, t p, e d n n e ev n ) I d ii n,i t r u to s i t o u e o e p a n t e p o e s o e e v n o e s o s n i g a d r c i i g . n a d to n e r p in i n r d c d t x li h r c s fr c ii g i f r t n F n l r v d e wo k i t r a e s a d r b s d o h r e . n o ma i . i a l we p o i e a n t r n e f c t n a d, a e n Et e n t o y Ke r s n t r e ie d i e , mb d e o t r Ar — i u CS 9 0 y wo d : e wo k d v c r r e e d d s fwa e, m L n x, 8 0 v
ARMlinux启动分析-Nathan.Yu的专栏-CSDN博客
ARMlinux启动分析-Nathan.Yu的专栏-CSDN博客linux启动分析(1)---bootloader启动内核过程我分析的是2.4.19的内核版本,是xscale的平台,参考了网上很多有价值的帖子,也加入了自己的一些看法,陆续总结成文字,今天是第一篇:内核一般是由bootloader来引导的,通过bootloader启动内核一般要传递三个参数,第一个参数放在寄存器0中,一般都为0,r0 = 0;第二个参数放在寄存器1中,是机器类型id,r1 = Machine Type Number;第三个参数放在寄存器2中,是启动参数标记列表在ram中的起始基地址;bootloader首先要将ramdisk(如果有)和内核拷贝到ram当中,然后可以通过c语言的模式启动内核:void (*startkernel)(int zero, int arch, unsigned int params_addr) = (void(*)(int, int, unsigned int))KERNEL_RAM_BASE;startkernel(0, ARCH_NUMBER, (unsigned int)kernel_params_start);其中KERNEL_RAM_BASE为内核在ram中启动的地址,ARCH_NUMBER是Machine Type Number,kernel_params_start 是参数在ram的偏移地址。
这时候就将全力交给了内核。
linux启动分析(2)---内核启动地址的确定内核编译链接过程是依靠vmlinux.lds文件,以arm为例vmlinux.lds文件位于kernel/arch/arm/vmlinux.lds,但是该文件是由vmlinux-armv.lds.in生成的,根据编译选项的不同源文件还可以是vmlinux-armo.lds.in,vmlinux-armv-xip.lds.in。
Linux系统调用_详细全过程
system_call片段(续) system_call片段(续)
nobadsys:
… #调用系统调 call *sys_call_table(,%eax,4) #调用系统调 用表中调用号为eax 用表中调用号为eax的系统调用例程 eax的系统调用例程 #将返回值存入堆栈 堆栈中 movl %eax,EAX(%esp) #将返回值存入堆栈中 Jmp ret_from_sys_call
优点
编程容易, 编程容易,从硬件设备的低级编程中解脱出来 提高了系统的安全性, 提高了系统的安全性,可以先检查请求的正确性
5.1 Linux系统调用-功能 系统调用系统调用
用户程序 . . . . 系统调用 . . . .
陷入处理机构 1)保护处理 机现场 2)取系统调 用功能号并 寻找子程序 入口 3)恢复处理 机现场并返 回 入口地址表 A0 A2 ... Ai ... An
系统调用 服务例程
system_call()片段 system_call()片段
…
pushl %eax /*将系统调用号压栈* /*将系统调用号压栈*/ SAVE_ALL ... /*检查系统调用号 cmpl$(NR_syscalls), %eax /*检查系统调用号 Jb nobadsys $(/*堆栈中的eax eax设置为Movl $(-ENOSYS), 24(%esp) /*堆栈中的eax设置为ENOSYS, ENOSYS, 作为返回值 Jmp ret_from_sys_call
Linux系统调用-功能 系统调用系统调用
系统调用是用户态进入内核态的唯一入口:一夫 系统调用是用户态进入内核态的唯一入口: 当关,万夫莫开。常用系统调用: 当关,万夫莫开。常用系统调用:
Linux系统调用
Linux系统调⽤所谓系统调⽤是指操作系统提供给⽤户程序调⽤的⼀组“特殊”接⼝,⽤户程序可以通过这组“特殊”接⼝来获得操作系统内核提供的服务。
例如⽤户可以通过进程控制相关的系统调⽤来创建进程、实现进程调度、进程管理等。
在这⾥,为什么⽤户程序不能直接访问系统内核提供的服务呢?这是由于在 Linux 中,为了更好地保护内核空间,将程序的运⾏空间分为内核空间和⽤户空间(也就是常称的内核态和⽤户态),它们分别运⾏在不同的级别上,在逻辑上是相互隔离的。
因此,⽤户进程在通常情况下不允许访问内核数据,也⽆法使⽤内核函数,它们只能在⽤户空间操作⽤户数据,调⽤⽤户空间的函数。
但是,在有些情况下,⽤户空间的进程需要获得⼀定的系统服务(调⽤内核空间程序),这时操作系统就必须利⽤系统提供给⽤户的“特殊接⼝”——系统调⽤规定⽤户进程进⼊内核空间的具体位置。
进⾏系统调⽤时,程序运⾏空间需要从⽤户空间进⼊内核空间,处理完后再返回到⽤户空间。
Linux 系统调⽤部分是⾮常精简的系统调⽤(只有 250 个左右),它继承了 UNIX 系统调⽤中最基本和最有⽤的部分。
这些系统调⽤按照功能逻辑⼤致可分为进程控制、进程间通信、⽂件系统控制、系统控制、存储管理、⽹络管理、socket 控制、⽤户管理等⼏类。
在 Linux 中对⽬录和设备的操作都等同于⽂件的操作,因此,⼤⼤简化了系统对不同设备的处理,提⾼了效率。
Linux 中的⽂件主要分为 4种:普通⽂件、⽬录⽂件、链接⽂件和设备⽂件。
那么,内核如何区分和引⽤特定的⽂件呢?这⾥⽤到的就是⼀个重要的概念——⽂件描述符。
对于 Linux ⽽⾔,所有对设备和⽂件的操作都使⽤⽂件描述符来进⾏的。
⽂件描述符是⼀个⾮负的整数,它是⼀个索引值,并指向内核中每个进程打开⽂件的记录表。
当打开⼀个现存⽂件或创建⼀个新⽂件时,内核就向进程返回⼀个⽂件描述符;当需要读写⽂件时,也需要把⽂件描述符作为参数传递给相应的函数。
Arm Linux系统调用流程详细解析-SWI
分类: Linux Kernel2011-12-02 20:45 2586人阅读 评论(0) 收藏 举报 linuxalignmenttablevectorsystemapi
转自:/course/6_system/linux/Linuxjs/20090515/167024.html
Unix 系统通过向内核发出系统调用( system call)实现了用户态进程和硬件设 备之间的大部分接口。系统调用是操作系统提供的服务,用户程序通过各种系统 调用,来引用内核提供的各种服务,系统调用的执行让用户程序陷入内核,该陷 入动作由 swi 软中断完成。 应用编程接口(API)与系统调用的不同在于,前者只是一个函数定义,说明了 如何获得一个给定的服务,而后者是通过软件中断向内核发出的一个明确的请 求。POSIX 标准针对 API,而不针对系统调用。Unix 系统给程序员提供了很多 API 库函数。 libc 的标准 c 库所定义的一些 API 引用了封装例程( wrapper routine) (其唯一目的就是发布系统调用)。 通常情况下, 每个系统调用对应一个封装例程, 而封装例程定义了应用程序使用的 API。反之则不然,一个 API 没必要对应一个 API 和系统调用之间的差别是没有关系的: 特定的系统调用。 从编程者的观点看, 唯一相关的事情就是函数名、参数类型及返回代码的含义。然而,从内核设计者 的观点看,这种差别确实有关系,因为系统调用属于内核,而用户态的库函数不 属于内核。 大部分封装例程返回一个整数,其值的含义依赖于相应的系统调用。返回-1通常 表示内核不能满足进程的请求。 系统调用处理程序的失败可能是由无效参数引起 的, 也可能是因为缺乏可用资源, 或硬件出了问题等等。 在 libd 库中定义的 errno 变量包含特定的出错码。每个出错码定义为一个常量宏。 当用户态的进程调用一个系统调用时, CPU 切换到内核态并开始执行一个内核 函数。因为内核实现了很多不同的系统调用,因此进程必须传递一个名为系统调 用号(system call number)的参数来识别所需的系统调用。所有的系统调用都 返回一个整数值。这些返回值与封装例程返回值的约定是不同的。在内核中, 整 数或0表示系统调用成功结束,而负数表示一个出错条件。在后一种情况下,这 个值就是存放在 errno 变量中必须返回给应用程序的负出错码。 ARM Linux 系统利用 SWI 指令来从用户空间进入内核空间,还是先让我们了解 下这个 SWI 指令吧。SWI 指令用于产生软件中断,从而实现从用户模式变换到
linux内核剖析(六)Linux系统调用详解(实现机制分析)
linux内核剖析(六)Linux系统调⽤详解(实现机制分析)本⽂介绍了系统调⽤的⼀些实现细节。
⾸先分析了系统调⽤的意义,它们与库函数和应⽤程序接⼝(API)有怎样的关系。
然后,我们考察了Linux内核如何实现系统调⽤,以及执⾏系统调⽤的连锁反应:陷⼊内核,传递系统调⽤号和参数,执⾏正确的系统调⽤函数,并把返回值带回⽤户空间。
最后讨论了如何增加系统调⽤,并提供了从⽤户空间访问系统调⽤的简单例⼦。
参考系统调⽤概述计算机系统的各种硬件资源是有限的,在现代多任务操作系统上同时运⾏的多个进程都需要访问这些资源,为了更好的管理这些资源进程是不允许直接操作的,所有对这些资源的访问都必须有操作系统控制。
也就是说操作系统是使⽤这些资源的唯⼀⼊⼝,⽽这个⼊⼝就是操作系统提供的系统调⽤(System Call)。
在linux 中系统调⽤是⽤户空间访问内核的唯⼀⼿段,除异常和陷⼊外,他们是内核唯⼀的合法⼊⼝。
⼀般情况下应⽤程序通过应⽤编程接⼝API,⽽不是直接通过系统调⽤来编程。
在Unix世界,最流⾏的API是基于POSIX标准的。
操作系统⼀般是通过中断从⽤户态切换到内核态。
中断就是⼀个硬件或软件请求,要求CPU暂停当前的⼯作,去处理更重要的事情。
⽐如,在x86机器上可以通过int指令进⾏软件中断,⽽在磁盘完成读写操作后会向CPU发起硬件中断。
中断有两个重要的属性,中断号和中断处理程序。
中断号⽤来标识不同的中断,不同的中断具有不同的中断处理程序。
在操作系统内核中维护着⼀个中断向量表(Interrupt Vector Table),这个数组存储了所有中断处理程序的地址,⽽中断号就是相应中断在中断向量表中的偏移量。
⼀般地,系统调⽤都是通过软件中断实现的,x86系统上的软件中断由int $0x80指令产⽣,⽽128号异常处理程序就是系统调⽤处理程序system_call(),它与硬件体系有关,在entry.S中⽤汇编写。
接下来就来看⼀下Linux下系统调⽤具体的实现过程。
Linux系统调用与ptrace分析(实验报告)_[文档在线提供]
Linux系统调用与ptrace分析概述1.Linux的系统结构在Linux系统结构中,最核心的是计算机硬件,它提供对Linux软件的支持,靠近硬件的内层是Linux内核程序(即操作系统)。
内核直接和硬件打交道是程序和硬件之间的接口或界面。
它对一切外层程序提供公共服务,把外部程序同硬件隔离开。
内核程序大致可分为文件系统管理,进程管理,内存管理等几部分。
进程管理又分为低级进程管理和高级进程管理。
低级进程管理主要包括:进程调度分配,控制占用处理器的程序和基本的进程通信。
高级进程管理主要包括:进程的创建,终止,进程间通信,进程在内存和外存之间的转储,信号机构和进程间跟踪控制等。
内核程序的外层是实用程序,内核提供对实用程序的支持,两层之间的界面是系统调用。
内核外的实用程序通过系统调用来和内核打交道。
实现的过程是通过一种特殊的指令(陷入指令)进入内核,然后转入相应的系统调用处理程序。
这也是本文将主要讨论的问题。
2.80386体系结构80386的体系结构承认两类事件。
1.异常(exceptions)2.中断(interrupts)他们两都会引起“上下文转换”同时建立一个过程或任务,中断可以随时随地发生(包括在执行程序时)所以用来响应硬件信号。
而异常则由指令内部错误引起。
每一个异常或中断都有一个唯一的标识符,在linux中被称为向量。
指令内部异常和NMI(不可屏蔽中断)的中断向量的范围从0—31。
32-255的任何向量都可以用做1.可屏蔽中断2.编程(调试)异常至于可屏蔽中断则取决于该系统的硬件配置。
外部中断控制器在中断响应周期把中断向量放到总线上。
3.Linux系统调用流程概述L inux系统调用的流程非常简单,它由0x80号中断进入系统调用入口,通过使用系统调用表保存系统调用服务函数的入口地址来实现,本文首先分析一般Linux系统调用的流程,然后再分析Linux系统调用sys_ptrace().一. Linux系统调用的流程分析1.1设定0x80号中断系统启动后,先进行初始化,其中一部分重要的工作在start_kernel()函数(main.c中定义)中进行,在该函数中先做必要的初始化工作(setup_arch()与paging_init()),各种trap入口就在该函数中通过调用trap_init()(traps.c)被设置,其中与系统调用有关的是:set_system_gate(0x80,&system_call);“set_system_gate()”是一宏,它在“system.h”中被定义:#define set_system_gate(n,addr) \_set_gate(&idt[n],15,3,addr)……中断描述表结构(head.s)其中“_set_gate()”也是在该文件中定义的宏:#define _set_gate(gate_addr,type,dpl,addr) \__asm__ __volatile__ ("movw %%dx,%%ax\n\t" \"movw %2,%%dx\n\t" \"movl %%eax,%0\n\t" \"movl %%edx,%1" \:"=m" (*((long *) (gate_addr))), \"=m" (*(1+(long *) (gate_addr))) \:"i" ((short) (0x8000+(dpl<<13)+(type<<8))), \"d" ((char *) (addr)),"a" (KERNEL_CS << 16) \:"ax","dx")调用该宏,将使addr地址值置入gate_addr中的地址值所指向的内存单元中,以上过程,使中断向量描述表中的第128项(16进制第80项)保存了0x80号中断的中断服务程序,即system_call。
ARM linux系统调用的实现原理
ARM linux系统调用的实现原理大家都知道linux的应用程序要想访问内核必须使用系统调用从而实现从usr模式转到svc模式。
下面咱们看看它的实现过程。
系统调用是os操作系统提供的服务,用户程序通过各种系统调用,来引用内核提供的各种服务,系统调用的执行让用户程序陷入内核,该陷入动作由swi软中断完成。
at91rm9200处理器对应的linux2.4.19内核系统调用对应的软中断定义如下:#if defined(__thumb__) //thumb模式#define __syscall(name) \"push {r7}\n\t" \"mov r7,#" __sys1(__NR_##name) "\n\t" \"swi 0\n\t" \"pop {r7}"#else //arm模式#define __syscall(name) "swi\t" __sys1(__NR_##name) "\n\t"#endif#define __sys2(x) #x#define __sys1(x) __sys2(x)#define __NR_SYSCALL_BASE 0x900000 //此为OS_NUMBER << 20运算值#define __NR_open (__NR_SYSCALL_BASE+ 5) //0x900005举一个例子来说:open系统调用,库函数最终会调用__syscall(open),宏展开之后为swi #__NR_open,即,swi #0x900005触发中断,中断号0x900005存放在[lr,#-4]地址中,处理器跳转到arch/arm/kernel/entry- common.S中vector_swi读取[lr,#-4]地址中的中断号,之后查询arch/arm/kernel/entry-common.S中的sys_call_table系统调用表,该表内容在arch/arm/kernel/calls.S中定义,__NR_open 在表中对应的顺序号为__syscall_start:....long SYMBOL_NAME(sys_open) //第5个...将sys_call_table[5]中内容传给pc,系统进入sys_open函数,处理实质的open 动作注:用到的一些函数数据所在文件,如下所示arch/arm/kernel/calls.S声明了系统调用函数include/asm-arm/unistd.h定义了系统调用的调用号规则vector_swi定义在arch/arm/kernel/entry-common.Svector_IRQ定义在arch/arm/kernel/entry-armv.Svector_FIQ定义在arch/arm/kernel/entry-armv.Sarch/arm/kernel/entry-common.S中对sys_call_table进行了定义:.type sys_call_table,#objectENTRY(sys_call_table)#include "calls.S" //将calls.S中的内容顺序链接到这里源程序:ENTRY(vector_swi)save_user_regszero_fpget_scno //将[lr,#-4]中的中断号转储到scno(r7)arm710_bug_check scno,ip#ifdef CONFIG_ALIGNMENT_TRAPldr ip,__cr_alignmentldr ip,[ip]mcr p15,0,ip,c1,c0 @ update control register#endifenable_irq ipstr r4,[sp,#-S_OFF]! @ push fifth argget_current_task tskldr ip,[tsk,#TSK_PTRACE] @ check for syscall tracingbic scno,scno,#0xff000000 @ mask off SWI op-code//#define OS_NUMBER 9[entry-header.S]//所以对于上面示例中open系统调用号scno=0x900005//eor scno,scno,#0x900000//之后scno=0x05eor scno,scno,#OS_NUMBER << 20 @ check OS number //sys_call_table项为calls.S的内容adr tbl,sys_call_table @ load syscall table pointertst ip,#PT_TRACESYS @ are we tracing syscalls?bne __sys_traceadrsvc al,lr,ret_fast_syscall @ return addresscmp scno,#NR_syscalls @ check upper syscall limit//执行sys_open函数ldrcc pc,[tbl,scno,lsl #2] @ call sys_* routineadd r1,sp,#S_OFF2: mov why,#0 @ no longer a real syscallcmp scno,#ARMSWI_OFFSETeor r0,scno,#OS_NUMBER << 20 @ put OS number back bcs SYMBOL_NAME(arm_syscall)b SYMBOL_NAME(sys_ni_syscall) @ not private func/** This is the really slow path. We're going to be doing* context switches,and waiting for our parent to respond.*/__sys_trace:add r1,sp,#S_OFFmov r0,#0 @ trace entry [IP = 0]bl SYMBOL_NAME(syscall_trace)/*//2007-07-01 gliethttp [entry-header.S]//Like adr,but force SVC mode (if required).macro adrsvc,cond,reg,labeladr\cond \reg,\label.endm//对应反汇编://add lr,pc,#16 ; lr = __sys_trace_return*/adrsvc al,lr,__sys_trace_return @ return address add r1,sp,#S_R0 + S_OFF @ pointer to regs cmp scno,#NR_syscalls @ check upper syscall limit ldmccia r1,{r0 - r3} @ have to reload r0 - r3ldrcc pc,[tbl,scno,lsl #2] @ call sys_* routine b 2b__sys_trace_return:str r0,[sp,#S_R0 + S_OFF]! @ save returned r0 mov r1,spmov r0,#1 @ trace exit [IP = 1]bl SYMBOL_NAME(syscall_trace)b ret_disable_irq.align 5#ifdef CONFIG_ALIGNMENT_TRAP.type __cr_alignment,#object__cr_alignment:.word SYMBOL_NAME(cr_alignment)#endif.type sys_call_table,#objectENTRY(sys_call_table)#include "calls.S"。
- 1、下载文档前请自行甄别文档内容的完整性,平台不提供额外的编辑、内容补充、找答案等附加服务。
- 2、"仅部分预览"的文档,不可在线预览部分如存在完整性等问题,可反馈申请退款(可完整预览的文档不适用该条件!)。
- 3、如文档侵犯您的权益,请联系客服反馈,我们会尽快为您处理(人工客服工作时间:9:00-18:30)。
/*=============================================================================
* SWI handler
*-----------------------------------------------------------------------------
bic scno, scno, #0xff000000 @ mask off SWI op-code
eor scno, scno, #__NR_SYSCALL_BASE @ check OS number
stmdb sp!, {r4, r5} @ push fifth and sixth args
#elif defined(CONFIG_AEABI)
/*
* Pure EABI user space always put syscall number into scno (r7).
*/
A710( ldr ip, [lr, #-4] @ get SWI instruction )
*/
/* If we're optimising for StrongARM the resulting code won't
run on an ARM7 and we can save a couple of instructions.
--pb */
.align 5
cmp scno, #__NR_syscall - __NR_SYSCALL_BASE
cmpne scno, #NR_syscalls @ check range
stmloia sp, {r5, r6} @ shuffle args
movlo r0, r1
movlo r1, r2
ldrcc pc, [tbl, scno, lsl #2] @ call sys_* routine
add r1, sp, #S_OFF
2: mov why, #0 @ no longer a real syscall
cmp scno, #(__ARM_NR_BASE - __NR_SYSCALL_BASE)
ldreq scno, [lr, #-4]
#else
/* Legacy ABI only. */
ldr scno, [lr, #-4] @ get SWI instruction
A710( and ip, scno, #0x0f000000 @ check for SWI )
tst ip, #_TIF_SYSCALL_TRACE @ are we tracing syscalls?
bne __sys_trace
cmp scno, #NR_syscalls @ check upper syscall limit
adr lr, BSYM(ret_fast_syscall) @ return address
movlo r2, r3
movlo r3, r4
ldrlo pc, [tbl, scno, lsl #2]
b sys_ni_syscall
ENDPROC(sys_syscall)
*/
#define cond_syscall(x) asm(".weak\t" #x "\n\t.set\t" #x ",sys_ni_syscall")
如何产生系统软中断,实现系统调用呢?
ldrcc pc, [tbl, scno, lsl #2] @ call sys_* routine
#endif
enable_irq
get_thread_info tsk
adr tbl, sys_call_table @ load syscall table pointer
ldr ip, [tsk, #TI_FLAGS] @ check for syscall tracing
/*
* "Conditional" syscalls
*
* What we want is __attribute__((weak,alias("sys_ni_syscall"))),
* but it doesn't work on all toolchains, so we just do it by hand
mrs r8, spsr @ called ห้องสมุดไป่ตู้rom non-FIQ mode, so ok.
str lr, [sp, #S_PC] @ Save calling PC
str r8, [sp, #S_PSR] @ Save CPSR
str r0, [sp, #S_OLD_R0] @ Save OLD_R0
/* Legacy ABI only, possibly thumb mode. */
tst r8, #PSR_T_BIT @ this is SPSR from save_user_regs
addne scno, r7, #__NR_SYSCALL_BASE @ put OS number in
eor r0, scno, #__NR_SYSCALL_BASE @ put OS number back
bcs arm_syscall
b sys_ni_syscall @ not private func
ENDPROC(vector_swi)
sys_syscall:
bic scno, r0, #__NR_OABI_SYSCALL_BASE
* value to determine if it is an EABI or an old ABI call.
*/
tst r8, #PSR_T_BIT
movne r10, #0 @ no thumb OABI emulation
ldreq r10, [lr, #-4] @ get SWI instruction
ENTRY(vector_swi)
sub sp, sp, #S_FRAME_SIZE
stmia sp, {r0 - r12} @ Calling r0 - r12
ARM( add r8, sp, #S_PC )
ARM( stmdb r8, {sp, lr}^ ) @ Calling sp, lr
A710( teq ip, #0x0f000000 )
A710( bne .Larm710bug )
#endif
#ifdef CONFIG_ALIGNMENT_TRAP
ldr ip, __cr_alignment
ldr ip, [ip]
mcr p15, 0, ip, c1, c0 @ update control register
zero_fp
/*
* Get the system call number.
*/
#if defined(CONFIG_OABI_COMPAT)
/*
* If we have CONFIG_OABI_COMPAT then we need to look at the swi
A710( and ip, ip, #0x0f000000 @ check for SWI )
A710( teq ip, #0x0f000000 )
A710( bne .Larm710bug )
#elif defined(CONFIG_ARM_THUMB)