Linux启动过程

(1)BIOS开机自检
对基础硬件环境进行开机自检,如:cpu,HDD,Memory,主板等, 根据启动顺序(默认硬盘启动)进行启动,只有自检通过才会进行下一步动作
(2)启动引导过程
系统启动后,会从硬盘0柱面0磁头1扇区(MBR主引导程序)读取引导启动程序(Boot loader)用于引导操作系统启动,当MBR加载到内存之后,BIOS讲控制权交给MBR(MBR中最主要的功能就是存储启动引导程序)
MBR(Master Boot Record)主引导程序,占用一个扇区大小共512字节,其中启动引导程序(linux启动引导程序GRUB2)占用446字节,分区表占用64字节(每个分区项占用16字节,这也是为什么硬盘最大只能分四个主分区的原因),最后还有2字节的结束标识
(3)加载内核及函数模块(驱动)
linux内核是以压缩的形成保存在启动系统的/boot目录下(被启动引导程序调用加载在内存中),内核会先在内存中进行解压缩,解压缩完成之后,内核会再执行一次自检(linux一般更信任内核自检)内核的自检过程会记录在"/var/log/dmesg"文件中,启动引导程序加载内核之后,就需要内核加载硬件的驱动程序,一般情况下IDE接口的硬盘驱动都保存在内核中,可以直接调用(IDE硬盘基本淘汰)这时候有一个问题,如果内核加载了所有的驱动程序,那么内核会非常大,linux的解决方案是把常用的驱动加载在内核中,把不常用的驱动做成函数模块加载放在/boot/lib/modules文件中,需要的时候进行调用。
(4)调用init进程
在硬件驱动成功后,Kernel会主动调用init进程(/sbin/init),而init会取得runlevel信息;
init执行/etc/rc.d/rc.sysinit文件来准备软件的操作环境(如网络、时区等);
init执行runlevel的各个服务的启动(script方式);
init执行/etc/rc.d/rc.local文件;
(5)登陆        
init执行终端机模拟程序mingetty来启动login程序,最后等待用户登录。

1、进程

进程启动过程

  当你在 shell 中敲入一个命令要执行时,内核会帮我们创建一个新的进程,它在往这个新进程的进程空间里面加载进可执行程序的代码段和数据段后,也会加载进动态连接器(在Linux里面通常就是 /lib/ld-linux.so 符号链接所指向的那个程序,它本省就是一个动态库)的代码段和数据。在这之后,内核将控制传递给动态链接库里面的代码。动态连接器接下来负责加载该命令应用程序所需要使用的各种动态库。加载完毕,动态连接器才将控制传递给应用程序的main函数。如此,你的应用程序才得以运行。(过程链接表(PLT),   Global Offset Table(GOT))

可执行文件结构

主要分为:.bss数据,.data数据,.text数据。

fork

  fork是用于创建子进程的系统调用,其调用一次但是返回两次。返回值大于0的为父进程,返回值等于0的为子进程,并且各自从fork函数处继续执行。
  子进程基本完全复制父进程:子进程id不同,文件描述符有自己的一份拷贝(引用计数会+1),超时时钟会清零,
  另外linux在此处使用了copy on write的技术。

fork和vfork

  fork是创建子进程的系统调用,子进程会完全复制父进程包括堆栈,它们只是pid不同。vfork也是创建子进程的系统调用,但是其创建的子进程是与父进程共享地址空间的,并且父进程会一直阻塞直至子进程调用exec或exit。

wait和waitpid

  wait会一直阻塞直到某一子进程终止。waitpid则可以等待指定子进程终止,还可以设置为非阻塞。
  waitpid中pid的含义依据其具体值而变:
pid==-1 等待任何一个子进程,此时waitpid的作用与wait相同
pid >0   等待进程ID与pid值相同的子进程
pid==0   等待与调用者进程组ID相同的任意子进程
pid<-1   等待进程组ID与pid绝对值相等的任意子进程

进程和线程区别

  1、启动一个新的进程必须分配给它独立的地址空间,建立众多的数据表来维护它的代码段、堆栈段和数据段,系统开销比较大,而线程不一样,它们彼此之间使用相同的地址空间,共享大部分数据,切换速度也比进程快,效率高,linux中线程和进程是共用一个数据结构struct_task的,所以linux中线程也就是lwp,开一个线程和进程底层都是使用的clone函数,只是共享的数据不同,线程共享大部分数据。
  2、由于进程之间独立的特点,使得进程安全性比较高,也因为进程有独立的地址空间,一个进程崩溃后,在保护模式下不会对其它进程产生影响,而线程只是一个进程中的不同执行路径。一个线程死掉回收堆空间往往导致连锁反应让整个进程死掉。
  3、体现在通信机制上面,正因为进程之间互不干扰,相互独立,进程的通信机制相对很复杂,譬如管道,信号,消息队列,共享内存,套接字等通信机制,而线程由于共享数据段所以通信机制很方便。

进程与线程的选择取决以下几点

  1、需要频繁创建销毁的优先使用线程;因为对进程来说创建和销毁一个进程代价是很大的。
  2、因为对CPU系统的效率使用上线程更占优,所以可能要发展到多机分布的用进程,多核分布用线程;
  3、需要更稳定安全时,适合选择进程;需要速度时,选择线程更好。

线程崩溃一定会导致进程崩溃吗?

  不一定,一般是因为共享地址空间带来的连锁反应,比如某一个线程堆栈溢出崩溃了,然后线程结束需要回收堆资源,导致其它共享地址空间的线程使用相应数据又发生了堆栈溢出。

线程间的共享和私有数据

共享数据:
1.文件描述符表(重点)
2.每种信号的处理方式(SIG_IGN、SIG_DFL或者自定义的信号处理函数)
3.当前工作目录
4.用户id和组id
5.地址空间

私有数据:
1.线程id
2.上下文信息,包括各种寄存器的值、程序计数器和栈指针(重点)
3.栈空间(临时变量存储在栈空间中)(重点)
4.errno变量
5.信号屏蔽字
6. 调度优先级

僵死进程,孤儿进程,守护进程

僵死进程:Linux中当一个进程结束后还会保留一定的信息,直到告知父进程自己终止了之后才会清除,所以当父进程一直不获取已经终止的子进程的状态时,该子进程就是一个僵尸进程,已经终止但还占用着一定的系统资源。
孤儿进程:父进程已经终止但是子进程还在运行,则这些子进程为孤儿进程,孤儿进程会由init进程收养并完成终止状态收集。
守护进程:在后台运行的进程,特点是独立于控制终端,周期性地执行某一任务或等待某一事件发生。

僵死进程怎么杀

  top指令发现僵尸进程,然后kill。

如何创建守护进程

1、fork然后exit退出父进程,目的是让自己不是进程组组长,以成功调用setsid。
2、setsid成为新会话的头头,目的是独立于终端。
3、chdir修改工作目录
4、重新设置文件权限掩码
5、关闭文件描述符
进程调度算法
FCFS(First Come First Serve):先来先服务。
时间片轮转算法
多级反馈队列算法
Linux的CFS算法

Linux进程可以被抢占吗

可以,两种情况:
1、有进程进入TASK_RUNNING状态,可以检查动态优先级进行抢占。
2、时间片到期时也可以被抢占。

实时操作系统与非实时操作系统

  实时操作系统的内核是内核可抢占,高优先级的任务立刻执行。非实时操作系统的内核是不可被抢占的。

Linux中进程有哪几种状态,ps中怎么查看

TASK_RUNNING(R):运行态和就绪态的进程;
TASK_INTERRUPTIBLE(S):可被中断的睡眠状态;
TASK_UNINTERRUPTIBLE(D):不可被中断的睡眠状态;
TASK_STOP/TASK_TRACED(T):中止状态,接收到SIGSTOP信号进入该状态,收到SIGCONT后继续执行。若有其它进程在追踪该进程,则为TASK_TRACED状态。
EXIT_DEAD(X):最终状态,进程终止并被父进程wait收集进程信息后进入该状态。
EXIT_ZOMBIE(Z):僵尸进程。

线程切换需要保存哪些上下文信息,SP,PC,PSW,EAX寄存器是干什么的

  需要保存线程id,堆栈指针,寄存器状态等信息。
  SP:栈顶指针。 bp:栈帧指针。PC:程序计数器,下一条要执行的指令。PSW:处理器状态字,与进程相关的硬件状态。EAX:累加寄存器。

线程间同步方式

  信号量:若信号量为0则挂起该线程,否则原子地减一。使用完资源后将信号量原子地加一,若有挂起的线程则将其唤醒。
  互斥量:又叫互斥锁,进入临界区需要加锁,离开临界区需要解锁。
  条件变量:用于线程间同步共享数据,当某个数据达到指定条件时,唤醒等待这个数据的一个或多个线程。

进程通信方式

(1)管道( pipe ):管道是一种半双工的通信方式,数据只能单向流动,而且只能在具有亲缘关系的进程间使用。进程的亲缘关系通常是指父子进程关系。
优点:简单方便。
缺点:局限于单向通信的工作方式.并且只能在创建它的进程及其子孙进程之间实现管道的共享。
(2)命名管道 (FIFO) :有名管道也是半双工的通信方式,但是它允许无亲缘关系进程间的通信。
优点:可以提供给任意关系的进程使用。
缺点:由于其长期存在于系统之中,使用不当容易出错。
(3)信号量:信号量是一个计数器,可以用来控制多个线程对共享资源的访问。它不是用于交换大批数据, 常作为一种锁机制,防止某进程在访问资源时其它进程也访问该资源,因此 , 主要作为进程间以及同一个进程内不同线程之间的同步手段。
优点:可作为同步手段。
缺点:无法传递数据。
(4)消息队列( message queue ) : 消息队列是由消息的链表,存放在内核中并由消息队列标识符标识。消息队列克服了信号传递信息少、管道只能承载无格式字节流以及缓冲区大小受限等缺点。(优先级,大小)
优点:允许任意进程通过共享消息队列来实现进程间通信.并由系统调用函数来实现消息发送和接收之间的同步,从而使得用户在使用消息缓冲进行通信时不再需要考虑同步问题,使用方便。
缺点:信息的复制需要额外消耗CPU的时间.不适宜于信息量大或操作频繁的场合。
(5)共享内存( shared memory ) :共享内存就是映射一段能被其他进程所访问的内存,这段共享内存由一个进程创建,但多个进程都可以访问。共享内存是最快的 IPC 方式,它是针对其他进程间通信方式运行效率低而专门设计的。它往往与其他通信机制,如信号量,配合使用,来实现进程间的同步和通信。
优点:快
缺点:需要用户保证同步
(6)信号 ( sinal ) : 信号是一种比较复杂的通信方式,用于通知接收进程某个事件已经发生,常见的信号。
(7)套接字( socket ) : 套解口也是一种进程间通信机制,与其他通信机制不同的是,它可用于不同机器间的进程通信。
优点:可以多机间通信。
缺点:

并发和并行

  并发指的是一段时间内CPU处理多个进程,多个事务,本质上还是串行的。
并行指的是同一时刻处理多个进程,真正地多个进程一起执行。

单核CPU多线程程序是否需要考虑加线程锁

  需要,单核CPU也可以并发执行多线程程序,也是存在同步与互斥的场景的。

死锁发生条件和解除方法

  死锁发生的四个条件:
互斥条件:一个资源只能被一个进程使用。
请求与保持条件:一个进程拥有一个资源,同时又请求另一资源,而另一资源被其它进程占有而使得该进程睡眠,却还保持着原有资源。
不可剥夺条件:当一个资源被一个进程占有时,另一进程不能抢夺该资源。
循环等待条件:若干进程形成首尾相接的资源等待关系。
  解除方法:
针对互斥条件:不可
针对请求与保持条件:一次分配所有所需资源。
针对不可剥夺条件:不可
针对循环等待条件:采用合理的资源分配算法,使进程请求资源不形成环路。如给所有资源编号,只有进程获得了编号较小的资源才能请求编号较大的资源。

如何排查死锁问题

  使用pstack工具,查看当前程序各线程栈情况,是不是存在某两个线程一直处于等锁状态。

互斥锁,递归锁,读写锁,自旋锁,RCU

  互斥锁:当需要访问互斥资源时,尝试获取锁,获取失败则睡眠,资源使用完后解锁,然后唤醒睡眠的线程。
  递归锁:同一线程可以多次获取一个锁资源,而当所有次数都被释放,其它线程才能获取该锁资源。一般来说,需要用到递归锁的情况说明设计是有问题的,尽量使用非递归锁。
  读写锁:读写锁有三种状态,读状态加锁,写状态加锁,未加锁。读状态锁可以由多个进程占有,而写状态锁只能由一个进程占有且当前没有进程占有读状态锁。
  自旋锁:当一个线程尝试获取某一锁资源失败时,它不会睡眠,而是忙等待该锁被其他线程释放。好处是效率高,少了睡眠唤醒的开销,缺点是占用CPU资源。
  RCU:Read-Copy-Updata,读数据不需要加锁,需要修改数据时,先拷贝一份,然后等到时机合适的时候通过回调机制将修改后的数据写回原处。

用户态和内核态

  用户态和内核态是进程运行的两种级别,用户态拥有最低的运行权限,内核态拥有最高的运行权限。
  用户态到内核态转变的方式:系统调用,异常,中断。

分用户态和内核态的目的

  用户程序是不可信的,比如对一些硬件进行操作,若操作不当将导致整个系统崩溃,所以这些操作需要交由内核完成。

gdb常用命令

r-bin重新开始运行可执行文件
b+num 在第num行下断点
n 逐过程单步调试
s 逐语句单步调试
l-num 查看第num行开始的原代码
c 继续运行
bt 查看函数调用堆栈
q 退出

Linux中的命令

stat查看文件的三个时间:
mtime:(modify)最后一次修改的时间;
atime:(access)最后一次访问的时间;
ctime:(change)最后一次修改状态的时间,如使用chmod修改权限;
netstat -anp |grep 端口号 查看端口号状态。
nload监控网络带宽;
grep是正则表达式命令,用于搜索文件中的特定字符串:grep [选项] “模式” [文件];
sed用于在特定处执行插入,删除,替换等操作:sed [选项] “命令” [文件];
awk用于在某种条件下执行某个操作:
图片1.png
ps:查看当前进程;
touch:创建文件;
chmod:修改权限;
“?”:可通配单个字符;
“*”:可通配任意多个字符;

系统调用开销大的原因

1、上下文切换
2、缓存局部性差
3、回到用户态的时候伴随一次任务调度
4、参数资源安全性检查

2、内存

内存模型

地址空间由低到高:.text代码段,.data数据段,.bss段,堆区,内存映射区,栈区,内核空间。

页式管理,段式管理,段页式管理

页式管理:
  将内存分成大小一样的页框,以页框为单位进行管理,进程启动的时候只会加载一部分页进物理内存,当某个虚拟内存数据真正被使用的时候才会产生缺页中断将相应的页加载进物理内存中。进行页式管理需要有一个页表进行虚拟地址到物理地址的转换,根据虚拟地址的高位找到相应的页所在的地址,然后根据低位找到数据所在位置。
  优点是没有外内存碎片,每个碎片大小不超过一个页。
  缺点是缺页中断的产生和选择淘汰页面等都要求有相应的硬件支持。

段式管理:
  将程序的逻辑分成不同的部分,每个部分为一个段,把进程的所有分段都存放在辅存中,进程运行时先把当前需要的一段或者几段装入主存,在执行过程中访问到不在主存的段时再把他们动态装入;
  优点是可以分开编写和编译,针对不同的段采取不同的保护策略。
  缺点是会产生碎片。

段页式管理:
  段式管理和页式管理的综合,每个段维护了一个页表结构。
  缺点:复杂,效率低。

内存泄漏如何检查

  使用一些工具如linux下的mtrace,或者对new/malloc进行统计监测,看和delete/free次数是否一致。

讲讲内存泄漏

  内存泄漏就是当分配的内存不再使用后没有得到释放。一般是new/malloc后没delete/free,或系统资源如socket没有close,或基类析构函数没有定义为虚函数。

段错误什么时候发生

  访问非法内存区域。

虚拟地址

  虚拟内存是计算机系统内存管理的一种技术。虚拟内存技术让每个进程都仿佛自己占用了系统的所有内存空间,而实际上所有进程共享物理内存,每个进程只是把当前需要使用的虚拟内存空间映射到了物理内存上,当实际访问数据时才会触发缺页中断拷贝数据。
  这样做的好处是操作系统能更方便更公平地管理内存,每个进程都有相同的虚拟内存大小;其次是扩大了地址空间,每个进程都仿佛自己占用了所有内存空间;然后是在程序需要连续空间时不需要连续的物理空间,只需要连续的虚拟空间即可。
这样做的代价是虚拟地址到物理地址的转换带来了时间消耗;其次页面的换入换出涉及到磁盘IO,效率很低。

页表寻址

  页式内存管理就是将内存分成一个个固定长度的页片,操作系统维护一个虚拟内存地址到物理地址映射关系的数据结构,叫做页表。先通过虚拟地址的高位找到页基地址,然后根据低位地址偏移到实际的物理地址。现代操作系统一般都采用多级页表,好处是可以离散存储页表,并且除非4G内存都用满了,否则相比线性页表节省空间;代价是增加了寻址次数。

缺页中断

  内存分配函数实际只是分配虚拟地址空间,并没有建立与物理内存的映射,当进程访问这些没有建立映射的虚拟地址时,会产生一个缺页中断,中断处理流程则和其它中断一样,缺页中断的处理函数会将相应的页加载到物理内存中。

页面置换算法

  局部页面置换算法:
FIFO:简单,有belady现象
LRU:效果最好,页面替换开销比较大
CLOCK:效果折中,页面替换开销比较小。将候选替换集合看作一个环形缓冲区,使用某一页面时候将其标志位置1,当需要替换一页时,从当前指针往后走,路过的页面标志位均置为0,直至遇到第一个标志位为0的页面,将其换出。

  全局页面置换算法:
工作集算法:使用一个时间窗口来表示工作集长度
页面错误高频算法:根据缺页率动态调整工作集长度

Linux伙伴系统

  Linux的伙伴系统是为避免外内存碎片而设计的算法,用于管理分配一组连续的物理页框。Linux中有11个页块链表,分别存放2i个连续页框的页块,申请的物理内存会向上取2i,然后在对应链表中寻找空闲页块,若该链表没有,则去下一个链表寻找,找到后将页块拆分,一部分下放给较小的页块链表,一部分分配使用。称为伙伴有三个条件,页块大小一样,物理地址连续,块必须是从同一个大块中分离出来的;若是伙伴,则可以合并为一个更大的块。

Linux slab层

  Linux的slab缓存是其分配对象的一种机制,其会预先分配好一块块大内存,其中存放缓存对象,申请对象的时候从此处分配,释放对象的时候返还给slab层而不归还给伙伴系统。它做了这么几个优化:
(1)会做内存对齐,根据标志设置为缓存行大小对齐,或按指针大小对齐。
(2)以不同的偏移量给缓存着色,使得各个slab对象的偏移量不同,在不同的缓存行。
(3)优先使用刚释放的对象,该对象大概率还在缓存中,提高命中率。

Linux malloc大内存

两种方法:
  扩展swap区,创建用于交换的磁盘文件;设置swap区文件;
  设置overcommit:设置内存分配方式:
  overcommit_memory是内核对内存分配的一种策略,它有三个可选值:0、1、2。
0. 表示内核将检查是否有足够的可用内存供应用进程使用;如果有足够的可用内存,内存申请允许;否则,内存申请失败,并把错误返回给应用进程。

  1. 表示内核允许分配所有的物理内存,而不管当前的内存状态如何。
  2. 表示内核允许分配超过所有物理内存和交换空间总和的内存;
    设置最大可分配的虚拟内存空间;

kmem_cache_alloc和kmalloc

  kmem_cache_alloc:直接从slab中分配内存。
  kmalloc:动态分配内存,实际上也是使用kmem_cache_alloc,预先设置了很多种大小的内存块数组,都是从slab层进行的申请内存,所以其实是会产生内部碎片的。另外,查找应使用哪个数组时使用的遍历而不是二分,如果size是常数的话,可被编译器优化为O1,否则是On。

字节对齐原因

  字节对齐的原因是很多CPU都只从对齐的地址开始加载数据,外部总线每次读取数据也不是一字节一字节地读取的,而是每次读取4字节或更多。这样一个int型变量若放在奇数地址,那么CPU需要读取两次才能解析出这个变量。

内存对齐规则

  内存对齐有三条规则:
每个成员变量在其对齐数的整数倍地址处;
每个成员变量的对齐数为编译器默认对齐数和该成员大小的较小值,若该成员为结构体,则其对齐数为该结构体的最大对齐数;
结构体的大小为其最大对齐数的整数倍。

#pragma pack(n) 相关预编译指令可以修改默认对齐数。

A* a = new A; a->i = 10;在内核中的内存分配上发生了什么?

  在栈区分配一个指针大小的空间给a,然后在堆区分配A大小的空间并将分配的空间地址赋值给变量a,然后根据a记录的地址和i在类A中的偏移量找到i的地址,并在该地址上给i赋值。

给你一个类,里面有static,virtual,之类的,来说一说这个类的内存分布

  静态成员:初始化了的在.data段,未初始化的在.bss段。
函数:在代码段。
  虚函数表:在.rodata只读数据段。
  普通成员:在实例化的对象中按声明顺序排列。

大端和小端

大端:低位字节放在高位地址
小端:低位字节放在低位地址
网络字节流:大端
判断方法:使用联合体,union T{int i; char c;}; T t;t.i = 1;判断t.c是否为1,为1则为小端。

静态变量何时初始化

C:程序执行前
C++:首次调用前

3、IO

五种IO模型

阻塞IO:IO函数处阻塞,什么也不做
非阻塞IO:IO函数处立即返回,需要用户轮询是否有数据
IO多路复用:内核来轮询多个非阻塞IO函数,有数据才真正地执行IO操作
信号驱动:当数据准备好时,内核发送信号通知用户处理
异步IO:当有数据时,内核完成IO操作,然后通知用户

说说select和epoll

  都是IO多路复用机制,本质都是同步IO,select需要每次进行轮询所有关注的fd来获取被激活的fd,epoll则每次只会返回被激活的fd。

epoll原理

  epoll有三个函数:
  首先是epoll_create,这个函数会做两件事,一是分配一块连续的物理空间初始化eventpoll对象,eventpoll中包含一颗红黑树,一条准备就绪链表和一条溢出链表;二是获取一个匿名文件,分配struct file对象和与之对应的fd,将eventpoll对象放在struct file中的private成员中,这样就可以通过fd找到struct file从而找到eventpoll对象,实现unix的一切皆文件。
  其次是epoll_ctl,这个函数可以用于添加,修改和移除事件,以添加为例,首先搜索红黑树判断新添加的fd是否已经存在,若不存在则从slab层分配一个epitem对象挂在红黑树上,然后根据epitem创建一个epoll_entry对象挂在相应设备的等待队列上,并设置事件发生时的回调函数。回调函数内容为将被激活的事件挂在eventpoll的就绪队列或溢出队列上,然后唤醒epoll_wait。
  最后是epoll_wait,检查就绪队列,是否为空,若不为空则先处理就绪队列,逐个拷贝到用户空间,然后将溢出队列对象挂到就绪队列上;若使用了LT模式,则会将拷贝到用户空间的epitem再次挂到就绪队列上,下次处理就绪队列时,若发现没有新事件则不拷贝到用户空间。

epoll的ET模式为什么一定要使用非阻塞IO

  ET模式下,有读事件的时候必须要将所有数据读完,所以要read到返回EAGAIN错误。而如果使用阻塞IO,这个时候线程就会阻塞。

为什么要有page cache,操作系统怎么设计的page cache

  page cache是将一部分磁盘文件缓存到内存中进行读写,从而减少磁盘IO。当要读取文件时,先从page cache中查找是否有所需文件,若没有命中才到磁盘中读取。写过的内存页会被标记为dirty page,操作系统会周期性地将dirty page回写到磁盘中。

如何修改fd上限

  Linux中是ulimit -n xxx

软链接和硬链接

  硬链接:多个文件项指向同一文件(inode,元数据)
  软链接:多个文件项指向不同的文件(不同的inode和元数据),但是所指的文件内容是同一文件的路径。

Linux的定时器

  2.6.16之前使用的是基于系统周期性时钟中断的多级时间轮,精度不高,只能到时钟中断的频率级别。
  2.6.16之后推出了高精度的定时器,使用的是红黑树,利用高精度时钟硬件设置下次中断触发的时间。

Linux下文件的cp和mv

  cp拷贝了一份文件。
  在相同的文件系统下,mv只是修改了几个指针,并没有实质性的拷贝删除文件,所以速度很快;在不同的文件系统下,mv则拷贝删除了文件,和cp等价。

rm一个正在被使用的文件会发生什么

  Linux每个文件对应有两个计数器,在inode结构体中分别是i_nlink和i_count,rm只会将i_nlink减一,所以使用ls看不到文件信息,但是正在使用该文件的进程还能操作该文件,因为该文件并没有真正的被删除,当进程关闭该文件资源后,i_count减一,当这两个计数器均为0后才会真正删除该文件。

文件IO与标准IO的区别

  文件IO也叫不带缓冲区的IO,直接调用OS的IO系统调用,与特定的OS绑定。
  标准IO是ANSI C建立的标准IO模型,linux下使用的是GLIBC库中的IO函数,它会为IO操作建立一个缓冲区,以减少系统调用的次数。

零拷贝

  核心是取消内核空间与用户空间之间的拷贝,如linux的sendfile系统调用:
2.4之前:
图片2.png
2.4之后:
图片3.png
2.4之后取消了页缓存到socket缓存的拷贝,直接传送地址和偏移。

Linux中的终端文件和黑洞文件

  终端文件:/dev/tty
  黑洞文件:/dev/null

上半部和下半部

  中断处理程序分为上半步和下半部:
  上半部是关闭中断的,执行中断处理程序,一般对时间敏感,和硬件相关,不可被打断的程序放在上半部执行;
  下半部是可以被中断的,一般有软中断,tasklet,工作队列的方式:
  (1)软中断:软中断保留给系统中对时间要求最严格以及最重要的下半部使用。目前只有两个子系统直接使用软中断:网络和SCSI。软中断不会抢占另外一个软中断,唯一可以抢占软中断的是中断处理程序。可以并发运行在多个CPU上(即使同一类型的也可以)。所以软中断必须设计为可重入的函数(允许多个CPU同时操作), 因此也需要使用自旋锁来保护其数据结构。软中断运行在中断上下文,软中断处理函数中不能睡眠。
  (2)Tasklet:由于软中断必须使用可重入函数,这就导致设计上的复杂度变高,而如果某种应用并不需要在多个CPU上并行执行,那么软中断其实是没有必要的。Tasklet的串行化使tasklet函数不必可重入,不会有同一个tasklet的多个实例并行的运行,因为它们只运行一次。 tasklet运行在中断上下文,tasklet处理函数中不能睡眠。
  (3)工作队列:工作队列可以把工作推后,交由一个内核线程去执行,也就是说,这个下半部分可以在进程上下文中执行。这样,通过工作队列执行的代码能占尽进程上下文的所有优势。最重要的就是工作队列允许被重新调度甚至是睡眠。
如果推后执行的任务需要睡眠,那么就选择工作队列。如果推后执行的任务不需要睡眠,那么就选择tasklet。

常见信号

  SIGINT,SIGKILL(不能被捕获),SIGSTOP(不能被捕获)、SIGTERM(可以被捕获),SIGSEGV,SIGCHLD,SIGALRM