0%

Linux进程、内核及文件系统总结

fork

关于linux进程间的close-on-exec机制

一般我们会调用exec执行另一个程序,此时会用全新的程序替换子进程的正文,数据,堆和栈等。此时保存文件描述符的变量当然也不存在 了,我们就无法关闭无用的文件描述符了。所以通常我们会fork子进程后在子进程中直接执行close关掉无用的文件描述符,然后再执行exec。
但是在复杂系统中,有时我们fork子进程时已经不知道打开了多少个文件描述符(包括socket句柄等),这此时进行逐一清理确实有很大难 度。我们期望的是能在fork子进程前打开某个文件句柄时就指定好:“这个句柄我在fork子进程后执行exec时就关闭”。其实时有这样的方法的:即所 谓 的 close-on-exec。
回到我们的应用场景中来,只要我们在创建socket的时候加上 SOCK_CLOEXEC标志,就能够达到我们要求的效果,在fork子进程中执行exec的时候,会清理掉父进程创建的socket。

Are child processes created with fork() automatically killed when the parent is killed?

No. If the parent is killed, children become children of the init process (that has the process id 1 and is launched as the first user process by the kernel).
The init process checks periodically for new children, and kills them if they have exited (thus freeing resources that are allocated by their return value).

How to make child process die after parent exits?

Child can ask kernel to deliver SIGHUP (or other signal) when parent dies by specifying option PR_SET_PDEATHSIG in prctl() syscall like this:
prctl(PR_SET_PDEATHSIG, SIGHUP);
See man 2 prctl for details.

Linux 技巧:让进程在后台可靠运行的几种方法

线程实现

LinuxThreads的不足

按照POSIX定义,同一进程的所有线程应该共享一个进程id和父进程id,这在目前的"一对一"模型下是无法实现的。
由于异步信号是内核以进程为单位分发的,而LinuxThreads的每个线程对内核来说都是一个进程,且没有实现"线程组",因此,某些语义不符合POSIX标准,比如没有实现向进程中所有线程发送信号,README对此作了说明。
LinuxThreads将每个进程的线程最大数目定义为1024,但实际上这个数值还受到整个系统的总进程数限制,这又是由于线程其实是核心进程。
管理线程容易成为瓶颈,这是这种结构的通病;同时,管理线程又负责用户线程的清理工作,因此,尽管管理线程已经屏蔽了大部分的信号,但一旦管理线程死亡,用户线程就不得不手工清理了,而且用户线程并不知道管理线程的状态,之后的线程创建等请求将无人处理。
LinuxThreads中的线程同步很大程度上是建立在信号基础上的,这种通过内核复杂的信号处理机制的同步方式,效率一直是个问题。

其他的线程实现机制

LinuxThreads的问题,特别是兼容性上的问题,严重阻碍了Linux上的跨平台应用(如Apache)采用多线程设计,从而使得Linux上的线程应用一直保持在比较低的水平。在Linux社区中,已经有很多人在为改进线程性能而努力,其中既包括用户级线程库,也包括核心级和用户级配合改进的线程库。目前最为人看好的有两个项目,一个是RedHat公司牵头研发的NPTL(Native Posix Thread Library),另一个则是IBM投资开发的NGPT(Next Generation Posix Threading),二者都是围绕完全兼容POSIX 1003.1c,同时在核内和核外做工作以而实现多对多线程模型。这两种模型都在一定程度上弥补了LinuxThreads的缺点,且都是重起炉灶全新设计的。

Linux 线程模型的比较:LinuxThreads 和 NPTL

LinuxThreads 的限制已经在 NPTL 以及 LinuxThreads 后期的一些版本中得到了克服。例如,最新的 LinuxThreads 实现使用了线程注册来定位线程本地数据;例如在 Intel® 处理器上,它就使用了 %fs 和 %gs 段寄存器来定位访问线程本地数据所使用的虚拟地址。尽管这个结果展示了 LinuxThreads 所采纳的一些修改的改进结果,但是它在更高负载和压力测试中,依然存在很多问题,因为它过分地依赖于一个管理线程,使用它来进行信号处理等操作。
您应该记住,在使用 LinuxThreads 构建库时,需要使用 -D_REENTRANT 编译时标志。这使得库线程是安全的。
最后,也许是最重要的事情,请记住 LinuxThreads 项目的创建者已经不再积极更新它了,他们认为 NPTL 会取代 LinuxThreads。
LinuxThreads 的缺点并不意味着 NPTL 就没有错误。作为一个面向 SMP 的设计,NPTL 也有一些缺点。我曾经看到过在最近的 Red Hat 内核上出现过这样的问题:一个简单线程在单处理器的机器上运行良好,但在 SMP 机器上却挂起了。我相信在 Linux 上还有更多工作要做才能使它具有更好的可伸缩性,从而满足高端应用程序的需求。

SIGNAL

Clarification on SIGKILL, SIGTERM, SIGINT, SIGQUIT, SIGSTP and SIGHUP

  • SIGKILL: kill -9
  • SIGTERM: kill
  • SIGINT: Ctrl+C (The difference between SIGINT and SIGTERM is that the former can be sent from a terminal as input characters.)
  • SIGQUIT Ctrl+\ (generates a core dump of the process and also cleans up resources held up by a process.)
  • SIGSTP Ctrl+Z (Suspends a process. The process can be resumed by sending a SIGCONT signal.)
  • SIGHUP Ctrl+D (Hangs up a process when the controlling terminal is disconnected.)

Linux内核信号处理机制介绍

如果想要进程捕获某个信号,然后作出相应的处理,就需要注册信号处理函数。同中断类似,内核也为每个进程准备了一个信号向量表,信号向量表中记录着每个信号所对应的处理机制,默认情况下是调用默认处理机制。当进程为某个信号注册了信号处理程序后,发生该信号时,内核就会调用注册的函数。
信号是异步的,一个进程不可能等待信号的到来,也不知道信号会到来,那么,进程是如何发现和接受信号呢?实际上,信号的接收不是由用户进程来完成的,而是由内核代理。当一个进程P2向另一个进程P1发送信号后,内核接受到信号,并将其放在P1的信号队列当中。当P1再次陷入内核态时,会检查信号队列,并根据相应的信号调取相应的信号处理函数。

内核态与用户态

Intel x86架构的CPU来说一共有0~3四个特权级,0级最高,3级最低,硬件上在执行每条指令时都会对指令所具有的特权级做相应的检查,相关的概念有CPL、DPL和RPL
用户态切换到内核态的3种方式:系统调用、异常、外围设备的中断。其中系统调用可以认为是用户进程主动发起的,异常和外围设备中断则是被动的。
从触发方式上看,可以认为存在前述3种不同的类型,但是从最终实际完成由用户态到内核态的切换操作上来说,涉及的关键步骤是完全一致的,没有任何区别,都相当于执行了一个中断响应的过程,因为系统调用实际上最终是中断机制实现的,而异常和中断的处理机制基本上也是一致的。

涉及到由用户态切换到内核态的步骤主要包括:

  1. 从当前进程的描述符中提取其内核栈的ss0及esp0信息。
  2. 使用ss0和esp0指向的内核栈将当前进程的cs,eip,eflags,ss,esp信息保存起来,这个过程也完成了由用户栈到内核栈的切换过程,同时保存了被暂停执行的程序的下一条指令。
  3. 将先前由中断向量检索得到的中断处理程序的cs,eip信息装入相应的寄存器,开始执行中断处理程序,这时就转到了内核态的程序执行了。

内核态程序执行完毕时如果要从内核态返回用户态,可以通过执行指令iret来完成,指令iret会将先前压栈的进入内核态前的cs,eip,eflags,ss,esp信息从栈里弹出,加载到各个对应的寄存器中,重新开始执行用户态的程序。

处理器总处于以下状态中的一种:

  1. 内核态,运行于进程上下文,内核代表进程运行于内核空间;
  2. 内核态,运行于中断上下文,内核代表硬件运行于内核空间;
  3. 用户态,运行于用户空间。

系统调用

strace常用来跟踪进程执行时的系统调用和所接收的信号。 在Linux世界,进程不能直接访问硬件设备,当进程需要访问硬件设备(比如读取磁盘文件,接收网络数据等等)时,必须由用户态模式切换至内核态模式,通过系统调用访问硬件设备。strace可以跟踪到一个进程产生的系统调用,包括参数,返回值,执行消耗的时间。
GDB则主要依赖一个系统函数ptrace。
Windows上的替代物则是WinDbg的Logger.exe和wt调试命令,以及Process Monitor则工具。

中断及IO调度

Linux 设备驱动开发 —— Tasklets 机制浅析

tasklet是I/O驱动程序中实现可延迟函数的首选方法。

中断处理程序&中断服务例程

Signals and interrupts a comparison

Interrupts can be viewed as a mean of communication between the CPU and the OS kernel. Signals can be viewed as a mean of communication between the OS kernel and OS processes.
Interrupts may be initiated by the CPU (exceptions - e.g.: divide by zero, page fault), devices (hardware interrupts - e.g: input available), or by a CPU instruction (traps - e.g: syscalls, breakpoints). They are eventually managed by the CPU, which “interrupts” the current task, and invokes an OS-kernel provided ISR/interrupt handler.
Signals may be initiated by the OS kernel (e.g: SIGFPE, SIGSEGV, SIGIO), or by a process(kill()). They are eventually managed by the OS kernel, which delivers them to the target thread/process, invoking either a generic action (ignore, terminate, terminate and dump core) or a process-provided signal handler.
Hardware interrupts can also generate signals, like a keyboard interrupt generates SIGINT. Thus interrupts and signals are closely tied to each other.

Linux 2.4.x内核软中断机制

硬中断、软中断

中断:通常被定义成一个事件,该事件改变处理器执行的指令顺序。这样的事件与cpu芯片外部电路产生的电信号相对应。
中断的产生:每个能够发出中断请求的硬件设备控制器都有一条称为IRQ的输出线(中断线)。所有的IRQ线都与一个中断控制器的输入引脚相连,中断控制器与cpu的intr引脚相连。
中断向量:每个中断由0-255之间的一个8位数来标识。称为中断向量。
中断描述符表:IDT是一个系统表,它与每一个中断或者异常向量相联系,每一个向量在表中有相应的中断处理程 序的入口地址。cpu的idtr寄存器执行IDT表的物理基地址。
硬中断是由硬件产生的,比如,像磁盘,网卡,键盘,时钟等。每个设备或设备集都有它自己的IRQ(中断请求)。基于IRQ,CPU可以将相应的请求分发到对应的硬件驱动上(注:硬件驱动通常是内核中的一个子程序,而不是一个独立的进程)。
中断的硬件处理:在内核被init进程初始化后,cpu运行在保护模式下。当执行一条指令后,sc和eip这对寄存器包含了下一条将要执行的指令的逻辑地址。在执行这条指令之前,cpu控制单元会检查在运行前一条指令时是否发生了一个中断。如果发生了,cpu控制单元处理中断。
中断与信号的区别:软中断通常是硬中断服务程序对内核的中断。信号则是由内核或者其他进程对某个进程的中断。
硬中断可以直接中断CPU。它会引起内核中相关的代码被触发。对于那些需要花费一些时间去处理的进程,中断代码本身也可以被其他的硬中断中断。
对于时钟中断,内核调度代码会将当前正在运行的进程挂起,从而让其他的进程来运行。它的存在是为了让调度代码(或称为调度器)可以调度多任务。
软中断的处理非常像硬中断。然而,它们仅仅是由当前正在运行的进程所产生的。
软中断并不会直接中断CPU。也只有当前正在运行的代码(或进程)才会产生软中断。这种中断是一种需要内核为正在运行的进程去做一些事情(通常为I/O)的请求。有一个特殊的软中断是Yield调用,它的作用是请求内核调度器去查看是否有一些其他的进程可以运行。
int n - 触发软中断n。相应的中断处理函数的地址为:中断向量表地址 + 4 * n。软件中断处理程序是由操作系统提供的为保证系统异步执行的机制。如在linux下由用户态向内核态转换需要调用0x80软件中断。软中断是实现系统API函数调用的手段。
中断嵌套:Linux下硬中断是可以嵌套的,但是没有优先级的概念,也就是说任何一个新的中断都可以打断正在执行的中断,但同种中断除外。软中断不能嵌套,但相同类型的软中断可以在不同CPU上并行执行。

文件系统

Linux 文件系统剖析
Linux 文件系统组件的体系结构

Linux 虚拟系统文件交换器剖析

linux文件系统中superblock,inode,dentry及file的关系

需要注意的几点如下所示:

  1. 进程每打开一个文件,就会有一个file结构与之对应。同一个进程可以多次打开同一个文件而得到多个不同的file结构,file结构描述被打开文件的属性,如文件的当前偏移量等信息。
  2. 两个不同的file结构可以对应同一个dentry结构。进程多次打开同一个文件时,对应的只有一个dentry结构。Dentry结构存储目录项和对应文件(inode)的信息。
  3. 在存储介质中,每个文件对应唯一的inode结点,但是每个文件又可以有多个文件名。即可以通过不同的文件名访问同一个文件。这里多个文件名对应一个文件的关系在数据结构中表示就是dentry和inode的关系。
  4. Inode中不存储文件的名字,它只存储节点号;而dentry则保存有名字和与其对应的节点号,所以就可以通过不同的dentry访问同一个inode。
  5. 不同的dentry则是同个文件链接(ln命令)来实现的。

How do file permissions apply to symlinks?
macos不考虑,一般的linux在chmod符号链接时其实作用于指向的文件本身,但chmod在有些情况下是会跳过涉及符号链接的情况的。

Symbolic link permissions
权限是记录在inode上的,符号链接最终指向的也是同一个inode。

EXT2 文件系统

我们将 inode 与 block 区块用图解来说明一下,如下图所示,文件系统先格式化出 inode 与 block 的区块,假设某一个文件的属性与权限数据是放置到 inode 4 号(下图较小方格内),而这个 inode 记录了文件数据的实际放置点为 2, 7, 13, 15 这四个 block 号码,此时我们的操作系统就能够据此来排列磁盘的阅读顺序,可以一口气将四个 block 内容读出来! 那么数据的读取就如同下图中的箭头所指定的模样了。
inode/block 数据存取示意图

这种数据存取的方法我们称为索引式文件系统(indexed allocation)。那有没有其他的惯用文件系统可以比较一下啊? 有的,那就是我们惯用的闪盘(闪存),闪盘使用的文件系统一般为 FAT 格式。FAT 这种格式的文件系统并没有 inode 存在,所以 FAT 没有办法将这个文件的所有 block 在一开始就读取出来。每个 block 号码都记录在前一个 block 当中, 他的读取方式有点像底下这样:
FAT文件系统数据存取示意图
上图中我们假设文件的数据依序写入1->7->4->15号这四个 block 号码中, 但这个文件系统没有办法一口气就知道四个 block 的号码,他得要一个一个的将 block 读出后,才会知道下一个 block 在何处。 如果同一个文件数据写入的 block 分散的太过厉害时,则我们的磁盘读取头将无法在磁盘转一圈就读到所有的数据, 因此磁盘就会多转好几圈才能完整的读取到这个文件的内容!

常常会听到所谓的『碎片整理』吧? 需要碎片整理的原因就是文件写入的 block 太过于离散了,此时文件读取的效能将会变的很差所致。 这个时候可以透过碎片整理将同一个文件所属的 blocks 汇整在一起,这样数据的读取会比较容易啊! 想当然尔,FAT 的文件系统需要经常的碎片整理一下,那么 Ext2 是否需要磁盘重整呢?

由于 Ext2 是索引式文件系统,基本上不太需要常常进行碎片整理的。但是如果文件系统使用太久, 常常删除/编辑/新增文件时,那么还是可能会造成文件数据太过于离散的问题,此时或许会需要进行重整一下的。

每天进步一点点——Linux中的文件描述符与打开文件之间的关系

SUID(Set UID)是让执行一个可执行程序的process拥有owner的权限,如passwd程序修改/etc/passwd文件。
SGID(Set GID)可以设置在目录上,也可以设置在文件上。设置在目录上是强制本目录下新建的(一级)文件和(一级)目录的group自动变为该目录的group。而设置在文件上,则是让执行一个可执行程序的process拥有该目录group的权限。
SBIT(Sticky Bit)设置在目录上,该目录下的(一级)文件和目录只有owner和root可以删除。

Linux的chattr和lsattr可以达到和NTFS权限一样复杂的功能。

The difference between fsync() and fdatasync() is that the later does not necessarily update the meta-data associated with a file – such as the “last modified” date – but only the file data.


本文地址:http://xnerv.wang/linux-process-kernel-and-file-system-summary/