linux内核的一些知识点(中)

1,813 阅读9分钟

系统调用

内核为用户进程提供的交互接口,可以为用户进程提供受限制地访问硬件设备、申请操作系统资源以及创建进程和进程通信等能力。linux中的每个系统调用都对应一个系统调用号,调用号用于指明具体哪个系统调用。

  • 系统调用既为应用程序提供了请求接口,又保证了系统的安全和稳定。用户空间的程序不能直接访问内核代码,内核代码驻留在受保护的内存地址中,用户进程无法访问这块内存。
  • 系统调用在用户进程和硬件设备之间增加一层,屏蔽了硬件的复杂操作,从而让应用层的使用更加便捷。

应用编程接口API

实际上应用程序一般会使用用户空间实现的应用编程接口来间接调用系统调用,API可以通过一个或若干个系统调用来实现一个接口,而且能屏蔽不同操作系统的差异,为应用程序提供相同的接口,比如常用的C库。此外,POSIX标注给出了API和系统调用之间的关系,不同的操作系统提供与POSIX兼容的库就能让应用程序实现统一的接口。

对硬件的管理

操作系统内核的核心任务包括对计算机扩展的硬件进行管理,比如硬盘、键盘、鼠标及其他扩展硬件。内核必须要能够与它们互相通信,但通信过程有个很关键的问题需要关注,就是速度问题。CPU的速度非常快,但向硬件发起一个请求并接到其响应的速度是相当慢的,如何有效让它们协同工作是硬件管理的核心问题,因为这个问题很可能会严重影响整体性能。

有两种实现方:轮询机制和中断机制。轮询机制是让内核定期对硬件设备进行查询,看是否需要处理,如果需要则处理,这种情况可能会让内核做很多无用功。中断机制则反过来,让硬件主动来发送信号,当硬件有事件发生时则向内核发出信号,然后内核再介入处理。内核一般也是使用中断机制来管理硬件。

关于中断

中断机制让硬件能发通知给CPU,中断本质是一个特殊的电信号,处理器接收到中断后会马上告知内核。比如点击下鼠标时鼠标控制器就会发送一个中断通知,处理器一旦检测到中断信号便中断自己当前的工作转而处理中断,然后通知操作系统鼠标产生了中断,内核得知鼠标被按下了,然后内核负责处理事件。

不同设备的中断不同,通过一个唯一的数字作为标识,也就是鼠标、键盘、硬盘的中断值都不同,操作系统对不同的中断进行处理。中断值也称中断请求线(IRQ),每个IRQ都对应一个数值,比如PC上0位时钟中断、1位键盘中断。当然,也可能是动态分配中断值。

中断由硬件打断操作系统,为硬件与操作系统提供了通信机制。

中断处理程序

内核在响应每个特定的中断时都会执行指定的一个函数,该函数为中断处理程序。每个硬件有一个相应的中断处理程序,他属于设备驱动的一部分。在linux中,中断处理程序是一个C函数,这些C函数按照一定的类型声明,然后内核就可以以标准的方式调用。中断处理程序被内核调用来响应中断,它们运行在中断上下文中,在该上下文中执行的代码不能够阻塞。

中断信号随时可能发生,所以中断处理程序随时可能被运行,必须保证它快速被执行,才能快速恢复中断代码的执行。理想状态是中断能快速被响应,而且中断处理程序能快速被执行完。但中断程序要处理的工作往往有很多,比如对于网络而言,中断处理程序要将网卡的数据包拷贝得到内存中,而且还要对其进行处理后才交给合适的协议栈,最终再告知硬件已处理中断信号。

中断的上半部和下半部

我们想要快速的中断响应,同时又想要在中断处理程序中完成更多的工作,这是一个矛盾体。为了解决这个问题,中断处理被分为上半部和下半部。上半部用于执行有严格时限的工作,比如应答硬件。而下半部用于处理能够延后处理的工作。上半部和下半部其实就是一种异步化处理思想,这样既能够保证响应速度,又能够完成大工作量的处理。

对于网卡来说,它的缓存大小是固定的,一旦网卡接收到数据后内核必须马上将它们拷贝到内存中,不然将可能导致网卡的缓存爆满而数据包被丢失。鉴于这种情况,对于网卡的中断信号处理应该快速将网卡数据包拷贝到内存中,这就是上半部的工作,快速执行完后马上结束中断处理,将处理器交还给中断前的程序。而耗时的数据包处理操作则放到下半部中,这部分可以稍后一点再处理,没有很强的时效性。

每个设备都有自己的驱动程序,驱动程序可以通过request_irq()函数来注册中断处理程序。

中断上下文

中断上下文是指内核在执行一个中断处理程序时所处的上下文,在该上下文中不能睡眠,也不能调用某些函数。中断处理程序时打断了其它正在执行的代码,所以它必须要快速简洁地执行完毕,中断处理程序有自己的栈。

linux中断过程

硬件产生了一个中断信号,它通过总线将电信号发送给中断控制器,中断控制器会将中断信号发往处理器。处理器会立即停止正在做的事情,然后关闭中断系统并跳到预定义的位置开始执行代码,这个预定义的代码就是由内核设置的中断处理程序入口。对于每个中断线,处理器都会跳转到对应一个唯一的入口位置。内核执行do_IRQ()函数对所接收到的中断进行响应。

系统定时器

内核大量函数都是基于时间驱动的,比如有些函数周期性地执行,这些就需要定时器来支持。系统定时器是一种可编程硬件芯片,它以固定频率产生中断,即定时器中断,它对应的中断处理程序负责更新系统时间,同时也负责执行周期性任务。定时器和时钟中断处理程序是linux内核管理机制的中枢。

定时器是管理内核流逝时间的基础,使用定时器时设置一个超时时间,并且指定超时发生时执行的函数。当定时器到时时会自动执行该函数,该函数只运行一次。

内核的时间

内核需要在硬件的帮助下才能计算和管理时间,硬件提供了系统定时器给内核来计算流逝的时间,当时钟中断发生时内核的特定中断处理程序会对其进行处理。定时器以某种频率自行触发时间中断,这个频率成为节拍率,连续两次时间中断的间隔时间为节拍。内核通过已知的节拍来计算墙上时间和系统运行时间,墙上时间即是实际时间,系统运行时间为自系统启动后开始的时间。内核也为用户空间提供了一组获取实际时间和日期的系统调用。

实时时钟

实时时钟(RTC)是用来持久存放系统时间的设备,它可以靠主板上的微型电池保持系统的计时,所以即使系统关闭了也能保持系统时间的计时。实时时钟和CMOS集成在一起,实时时钟与BIOS的保存设置都是通过同一个电池供电。系统启动时内核读取实时时钟来初始化墙上时间,该时间对应的变量为xtime。

基于时间的内核管理

内核很多工作都依赖于时钟中断,比如:

  • 系统运行时间的更新工作。
  • 实际时间的更新工作。
  • 在SMP结构中,均衡调度程序中运行队列的均衡工作。
  • 判断当前进程是否使用完了自己的时间片,调度工作。
  • 执行动态定时器。
  • 处理器时间的统计工作。

关于节拍率

系统定时器的节拍率提供给静态预处理定义,单位为HZ,内核再asm/param.h文件中定义。x86体系中系统定时器频率默认值为100,所以时钟中断评论为100HZ,每秒进行时钟中断100次,每10ms产生一次。不同的体系结构节拍率可能不同。

更高的节拍率能提供时间驱动时间的解析度,同时也提高时间驱动事件的准确度。对于linux来说能提供更高的精度来执行poll()、select()等系统调用,也能提高进程抢占的准确度。但高节拍率会增加系统负担,因为会更加频繁地执行时钟中断处理程序,增加了电源的消耗。

jiffies

jiffies是一个全局变量,它用来记录自系统启动以来产生的节拍总数。启动时为0,然后每次时钟中断都会增加改变了的值,每秒增量为节拍率,即n赫兹。时钟中断处理程序工作大致包括:

  • 获得xtime_lock锁,对jiffies_64和墙上时间xtime进行保护。
  • 应答系统时钟。
  • 使用墙上时间更新实时时钟。
  • 累加jiffies_64。
  • 更新当前进程消耗的系统时间和用户时间。
  • 执行已到期的定时器。
  • 计算平均负载。

专注于人工智能、读书与感想、聊聊数学、计算机科学、分布式、机器学习、深度学习、自然语言处理、算法与数据结构、Java深度、Tomcat内核等。