系统调用(Linux)
目录
1.内核态和用户态:
1.1CPU的两种状态:
1.2CPU 指令集权限:
1.3用户态与内核态的空间:
1.4用户态与内核态的切换:
2.系统调用:
2.1linux框架图:
2.2系统调用和API:
2.3系统调用号:
1.内核态和用户态:
1.1CPU的两种状态:
用户态: 运行用户程序
内核态:运行操作系统程序,操作硬件
简单来说,低权限的资源范围较小,高权限的资源范围更大
1.2CPU 指令集权限:
在说用户态与内核态之前,有必要说一下 C P U 指令集
,指令集是 C P U 实现软件指挥硬件执行的媒介,具体来说每一条汇编语句都对应了一条 C P U 指令
,而非常非常多的 C P U 指令
在一起,可以组成一个、甚至多个集合,指令的集合叫 C P U 指令集
。
同时 C P U 指令集
有权限分级,大家试想,C P U 指令集
可以直接操作硬件的,要是因为指令操作的不规范`,造成的错误会影响整个计算机系统的。好比你写程序,因为对硬件操作不熟悉,导致操作系统内核、及其他所有正在运行的程序,都可能会因为操作失误而受到不可挽回的错误,最后只能重启计算机才行。
而对于硬件的操作是非常复杂的,参数众多,出问题的几率相当大,必须谨慎的进行操作,对开发人员来说是个艰巨的任务,还会增加负担,同时开发人员在这方面也不被信任,所以操作系统内核直接屏蔽开发人员对硬件操作的可能,都不让你碰到这些 C P U 指令集
。
针对上面的需求,硬件设备商直接提供硬件级别的支持,做法就是对 C P U 指令集
设置了权限,不同级别权限能使用的 C P U 指令集
是有限的,以 Inter C P U 为例,Inter把 C P U 指令集
操作的权限由高到低划为4级:ring 0,ring 1,ring 2,ring 3.
其中 ring 0 权限最高,可以使用所有 C P U 指令集
,ring 3 权限最低,仅能使用常规 C P U 指令集
,不能使用操作硬件资源的 C P U 指令集
,Linux系统仅采用ring 0 和 ring 3 这2个权限。
- ring 0被叫做内核态,完全在操作系统内核中运行
- ring 3被叫做用户态,在应用程序中运行
1.3用户态与内核态的空间:
以Linux32位操作系统为例,它的寻址空间范围是 4G
(2的32次方),而操作系统会把虚拟控制地址划分为两部分,一部分为内核空间,另一部分为用户空间,高位的 1G
(从虚拟地址 0xC0000000 到 0xFFFFFFFF)由内核使用,而低位的 3G
(从虚拟地址 0x00000000 到 0xBFFFFFFF)由各个进程使用。
- 用户态:只能操作
0-3G
范围的低位虚拟空间地址 - 内核态:
0-4G
范围的虚拟空间地址都可以操作,尤其是对3-4G
范围的高位虚拟空间地址必须由内核态去操作 - 补充:
3G-4G
部分大家是共享的(指所有进程的内核态逻辑地址是共享同一块内存地址),是内核态的地址空间,这里存放在整个内核的代码和所有的内核模块,以及内核所维护的数据
每个进程的 4G
虚拟空间地址,高位 1G
都是一样的,即内核空间。只有剩余的 3G
才归进程自己使用,换句话说就是, 高位 1G
的内核空间是被所有进程共享的!
最后做个小结,我们通过指令集权限区分用户态和内核态,还限制了内存资源的使用,操作系统为用户态与内核态划分了两块内存空间,给它们对应的指令集使用
1.4用户态与内核态的切换:
- 系统调用:用户态进程主动切换到内核态的方式,用户态进程通过系统调用向操作系统申请资源完成工作,例如 fork()就是一个创建新进程的系统调用,系统调用的机制核心使用了操作系统为用户特别开放的一个中断来实现,如Linux 的 int 80h 中断,也可以称为软中断
- 异常:当 C P U 在执行用户态的进程时,发生了一些没有预知的异常,这时当前运行进程会切换到处理此异常的内核相关进程中,也就是切换到了内核态,如缺页异常
- 中断:当 C P U 在执行用户态的进程时,外围设备完成用户请求的操作后,会向 C P U 发出相应的中断信号,这时 C P U 会暂停执行下一条即将要执行的指令,转到与中断信号对应的处理程序去执行,也就是切换到了内核态。如硬盘读写操作完成,系统会切换到硬盘读写的中断处理程序中执行后边的操作等。
2.系统调用:
2.1linux框架图:
2.2系统调用和API:
Linux的系统调用像大多数Unix系统一样,作为C库的一部分提供,C库实现了Unix系统的主要API,包括标准C库函数和系统调用。所有的C程序都可以使用C库,而由于C语言本身的特点,其他语言也可以很方便地把它们封装起来使用。此外,C库提供了POSIX的绝大部分API。
如图:
从程序员的角度看,系统调用无关紧要﹔他们只需要跟API打交道就可以了。相反,内核只跟系统调用打交道﹔库函数及应用程序是怎么使用系统调用不是内核所关心的。但是,内核必须时刻牢记系统调用所有潜在的用途并保证它们有良好的通用性和灵活性。
关于Unix的界面设计有一句通用的格言“提供机制而不是策略”。换句话说,Unix的系统调用抽象出了用于完成某种确定目的的函数。至于这些函数怎么用完全不需要内核去关心。(区别对待机制(mechanism)和策略(policy)是Unix设计中的-大亮点。大部分的编程问题都可以被切割成两个部分:“需要提供什么功能”(机制)和“怎样实现这些功能”(策略)。如果由程序中的独立部分分别负责机制和策略的实现,那么开发软件就更容易,也更容易适应不同的需求。
2.3系统调用号:
在Linux中,每个系统调用被赋予一个系统调用号。这样,通过这个独一无二的号就可以关联系统调用。当用户空间的进程执行一个系统调用的时候,这个系统调用号就被用来指明到底是要执行哪个系统调用;进程不会提及系统调用的名称。
系统调用号相当关键,一旦分配就不能再有任何变更,否则编译好的应用程序就会崩溃。此外,如果一个系统调用被删除,它所占用的系统调用号也不允许被回收利用,否则,以前编译过的代码会调用这个系统调用,但事实上缺调用的是另一个系统调用。Linux有一个“未实现”系统调用sys_ni_syscall(),它除了返回-ENOSYS外不做任何其他工作,这个错误号就是专门针对无效的系统调用而设的。虽然很罕见,但如果-个系统调用被删除,或者变得不可用,这个函数就要负责“填补空位”。
因为所有的系统调用陷入内核的方式都一样,所以仅仅是陷入内核空间是不够的。因此必须把系统调用号一并传给内核。在x86上,系统调用号是通过eax寄存器传递给内核的。在陷入内核之前,用户空间就把相应系统调用所对应的号放入eax中了。这样系统调用处理程序一旦运行,就可以从eax中得到数据。其他体系结构上的实现也都类似。
给用户空间的返回值也通过寄存器传递。在x86系统上,它存放在eax寄存器中。
参考文章:
从根上理解用户态与内核态
《Linux内核设计和实现》