Linux内核学习总结

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Linux内核学习总结相关的知识,希望对你有一定的参考价值。

分内存和进程两部分。

内存


1) 查看内存及内存分配的几个命令:

a) nmon: 图形化的界面,不只能看内存,还可以看cpu,网络,内核,磁盘信息,内存可以看swap,slab, page table的使用量。

b) top=cat /proc/pid/statm ,里面比较重要的是virt/res/shr的三个数值,分别代表虚拟内存,物理内存,共享内存,注:top显示的是以mb为单位,statm是以页个数为单位,要乘以4k.

c) pmap <pid>:显示给pid分配的内存页面地址,应该是物理内存地址?以及对应的地址打开的文件。

d) strace

e) pstree -p, top -H -p, 前者查看子进程,后者加上H查看线程,但是有时信息输出是一样的,不是很明白,可以用pmap看一下,个人觉得如果分配了地址就是子进程了,子进程和线程的区别是线程共享父进程的内存空间,不需要用别的方法去做进程间通信了。

f)pstack -p <pid> 查看进程分配的内存地址和栈(函数调用)

g)slaptop, 查看slap缓存内容。

h)关于free命令第一行最后的buffer和cache,其实是还没有写入磁盘的bufer和当前存在内存的cache,算真实的free和used时候要相应加上和减去他俩,因为预估buffer/cache是可以很快释放的,

第一行最前面的free used是物理内存的free used,包含了后面的cache/buffer,只是预估他们会被释放掉。


2) 内存消耗的主要来源(b,c可以通过nmon命令查看,slap用slaptop看更直观)

   a. 进程消耗

   b. slab : 内核的模块在分配资源的时候,为了提高效率和资源的利用率,都是透过slab来分配,内核对经常操作的0~896M地址对象有个池,slab用来缓存这个池

   c. page table: 虚拟内存地址映射物理内存地址的一个页表,一直留在内存


3)linux系统中的几个内存相关缓存

    首先一个程序在系统调用exit()函数释放部分内存空间后,是不会立即释放回磁盘的,这样很浪费,会作为cache留在内存中,仍然消耗着内存,所以时间久了内存会不够用。

    a. bufer, 全称bufer cache,是用来写入时缓冲块设备的,cpu->bufer->磁盘

    b. cache, paging cache, 上面文字介绍的cache,用来读取时缓存文件的,cpu->cache,没有采取磁盘找。

     也就是free命令中显示的buffer/cache

   c. 其实还有目录的dentry和inode缓存,这个是vfs的概念,例如一个路径/home/xxx/yyy,这个就是dentry 缓存,dentry:将inode号和文件名联系起来,dentry还保存目录和其子对象的关系,用于文件系统的变量。dentry还起着缓存的作用,缓存最常使用的文件以便于更快捷的访问。

     释放缓存的方法: echo 1/2/3 > /proc/sys/vm/drop_cache ,1是drap paging缓存,2是inode和dentry,三是全部释放


4)虚拟内存与物理内存

 32位的系统默认每个进程可以分配4G的虚拟内存,达到了进程隔离的目的,分页管理实现了高效分配,固定物理地址分配的目的,在这4G空间中,0~3G分给用户空间,专门给用户程序使用,3~4G分给内核使用,如果物理内存大于4G,其中最低的0~16M映射给内核1G中的16M,是zone_DMA,给DMA借口设备用的,物理内存的17~896M映射给内核1G中的17~896M,称为zone_normal,用来存储内核常用的上下文,内核区域剩下的128M来临时映射到物理内存剩余的1G之上全部空间(高端内存,通过位移),这样内核就可以访问全部物理内存区域了,而用户区域的3G就只能访问物理内存中的3G(内核1G之外的)。注:只有zone_normal是内核可以直接使用的。


 a. 物理内存中的低端内存(低于1G)的由kmallock函数分配,虚拟内存的线性地址和对应的物理内存地址都是连续的,由两个算法:伙伴算法负责大块连续物理内存分配,slab算法负责小块内存分配,接口都是kmalloc函数

 b. 物理内存中的高端内存由vmallock函数分配,线性地址连续但是物理内存不连续,这样可以更有效的利用物理内存

c. mallock分配user空间内存。

 d. 内存分段管理的缺点:仍然是以程序为单位分段,在内存与swap交换时不能充分高效利用,产生外碎片。

e. 所有进程的内核区起始都是映射的同样地的低端内存,保存例如驱动,内核函数。

 

5) 在分页管理中心是如何映射虚拟线性地址与物理内存地址的

    linux最多支持4级分页,根据不同的cpu厂商,常用的有两级和三级

 a.三级:

    把线性地址从高到低分为: 最高10位是页目录地址,中间10位是页地址,最低12位是偏移offset地址。通过页目录地址找到页表的物理内存地址,通过页地址找到在物理内存中的起始地址,通过偏移找到具体的位置,页目录的物理地址在cpu中的cr3寄存器中(mmu去读),也就是依次找到物理地址中的页目录->页->页起始->页偏移,这个地址的转换是由mmu(内存管理单元)来完成的。

     mmu是cpu上的一个硬件,cpu要读取线性地址,首先会去mmu的TLB(缓存线性->物理),没有的话再读线性地址找到PA,也就是说所需要的内存页在物理内存中,如果读不到,就会报一个缺页异常,cpu会去swap分区把内存页交换回来加载到内存。

 b.二级:

      只有页目录和页表,没有offset,逻辑是一样的。


6)共享内存

两个进程的线性地址映射到同一个物理地址,但是没有同步机制,A进程要写的时候,B也可以去写,可以通过信号量才同步。


7)CPU与外设通信

 有三种方法:

 a. 状态查询,cpu会去逐个查询外设的状态信息,直到有外设发出停止信号,

 b-1. 硬中断,外设会发中IRQ的中断信号->cpu->驱动相应, 例如键盘按下等,这个属于系统调用的一种,用户态->内核态。

 b-2. 软中断, 由进程发出,直发给驱动。。

  c. DMA,外设不经过CPU直接与与内存通信。

IRQ:interupt request,中断请求,linux系统中2-14条req可用,一个外设连一条,同一时刻只能一个irq和cpu联系。


8) cpu读取内存顺序

a. 首先cpu内核发出读取VA请求,这个请求会先到mmu的TLB,里面换存折最近查询VA的记录和物理页,如果有直接把物理页传给内核,不需要翻译了。

b, 如果tlb没有,就要翻译之后去PA页读取了存在TLB(如果允许cache,这个权限在页表中设置了),再加载给内核。

c. 如果加载不到物理页就会引发缺页,去磁盘读了加载进物理页。


二部分:进程

一个进程至少一个线程。

1)创建进程都是创建了哪些数据:

   a. 文本区域:执行代码

   b.数据区域:动态分配的内存页+存储变量

   c. 堆栈,调用的函数和本地变量,堆由系统创建的函数和变量,堆是用户创建的函数和变量

  这三点也叫作上下文。


2)进程的状态:就绪(排队),运行,阻塞(等待I/O)


3)线程进程区别:

   线程不拥有数据区域,由堆栈,多个线程共享进程的数据区域。线程的意义在于一个应用程序中有多个执行部分在同时进行。


4)用户态,内核态,系统调用,进程切换

   用户态:只能访问3G的物理内存,不能访问低端内存区域。

   内核态: cpu可以访问所有内存空间,包括外设

   cpu由三个级别:ring0是内核,ring3是用户,12没有用到。

   系统调用:当用户进程中需要执行内核代码时,例如fork,进程结束的exit,或者调用外设,进程执行init80h来进行系统调用,将cpu ring由3切换到0,进入低端内存区域执行内核程序,完成后切换回去。

   用户态->内核态的方法:init80h系统调用,硬件中断,异常(例如缺页)。

   进程切换:可以看到系统调用主要是cpu状态的切换,而系统进程不同,cpu经常需要挂起一个程序,恢复之前程序,内核保存A进程的上下文,切换到B进程。

  进程切换与系统调用根本区别:看似差不多,但是系统调用只是cpu模式切换,上下文还是同一个进程,没有进程切换,调用结束查看队列如果有比当前进程优先级高的,保存当前进程,切换到新进程,改变上下文。


5) fork vfork clone cow

   fork创建进程,子进程拥有父进程一样内存空间数据,返回值是pid,给予独立的虚拟内存线性地址,vfork创建进程不给虚拟地址空间,共享父进程的地址空间,这已经是线程的逻辑了,而clone是linux给予创建线程的函数,可以像vfork那样创建线程,也可以创建非父子关系的进程。

  cow是写时复制,比如fork一个新进程,如果完全复制父进程的内存数据,而之后子进程并不修改页面,直接的复制白费了,所以用cow,类似存储的snapshoot,只是给于一个指针指向父进程的内存区域,如果有新的system call要写进程,再复制一份给他写,好处是如果只是读就不需要给他复制。进程结束不会立刻清理内存回磁盘,也是类似cow


6)僵尸进程

     僵尸进程是已经结束的进程,不占用内存空间,无执行代码,不能被调度,尽在进程列表中留一个位置,记录了退出状态,占用pid,太多了的话不利于系统调度。

     僵尸进程原因:子进程结束时,执行exit()系统调用,内核释放该进程资源,包括打开的文件,占用的内存,但仍然会保留一定信息,包括pid,退出码,退出状态,运行时间,一直等到父进程通过wait函数来如这个状态后才会完全释放,僵尸进程就是父进程没有来取。

     处理方法:

      a, kill 父进程,成为孤儿进程,由init进程调用wait函数释放。

      b. 父进程可以通过singal()信号通知内核,对子进程退出不关心,内核直接回收。


7) 进程/线程同步

    信号量:是进程间(线程间)同步用的,一个进程(线程)完成了某一个动作就通过信号量告诉别的进程(线程),别的进程(线程)再进行某些动作。有二值和多值信号量之分

     互斥锁:是线程间互斥用的,一个线程占用了某一个共享资源,那么别的线程就无法访问,直到这个线程离开,其他的线程才开始可以使用这个共享资源。可以把互斥锁看成二值信号量。  

     信号量主要适用于进程间通信,当然,也可用于线程间通信。而互斥锁只能用于线程间通信。

     跟互斥锁一样,一个执行单元要想访问被自旋锁保护的共享资源,必须先得到锁,在访问完共享资源后,必须释放锁。如果在获取自旋锁时,没有任何执行单元保持该锁,那么将立即得到锁;如果在获取自旋锁时锁已经有保持者,那么获取锁操作将自旋在那里,直到该自旋锁的保持者释放了锁。由此我们可以看出,自旋锁是一种比较低级的保护数据结构或代码片段的原始方式,这种锁可能存在两个问题:死锁和过多占用cpu资源自旋锁比较适用于锁使用者保持锁时间比较短的情况。正是由于自旋锁使用者一般保持锁时间非常短,因此选择自旋而不是睡眠是非常必要的,自旋锁的效率远高于互斥锁。

      自旋锁最多只能被一个可执行线程持有,如果一个执行线程试图请求一个已被争用(已经被持有)的自旋锁,那么这个线程就会一直进行忙循环——旋转——等待锁重新可用。要是锁未被争用,请求它的执行线程便能立刻得到它并且继续进行。自旋锁可以在任何时刻防止多于一个的执行线程同时进入临界区。
      Linux中的信号量是一种睡眠锁。如果有一个任务试图获得一个已被持有的信号量时,信号量会将其推入等待队列,然后让其睡眠。这时处理器获得自由去执行其它代码。当持有信号量的进程将信号量释放后,在等待队列中的一个任务将被唤醒,从而便可以获得这个信号量。
信号量的睡眠特性,使得信号量适用于锁会被长时间持有的情况;只能在进程上下文中使用,因为中断上下文中是不能被调度的;另外当代码持有信号量时,不可以再持有自旋锁

    信号量解释:

     信号量是一个计数器,进程/线程想要或许一个内存资源时,会先测试信号量,如果是正数代表可以用,如果是0代表不可用,用了以后,count-1, 用完count+1.

 

8)进程间通信

   a.管道(pipe)

      可以ls -al  /proc/pid/fd (文件目录),里面的信息由:

       1.该进程开启的文件

       2. socket:后面有inode号,cat /proc/net/tcp会有这个inode的二进制IP:port,代表当前进程网络连接情况

      3. pipe: 后跟一个inode,当前进程就通过这个inode代表的pipe,与另一个进程同步,另一个进程也开启了这个inode的pipe。如果同一个fd下面有两个相同inode的pipe,可能是这个进程下的多线程。


9)信号

  

 信号的类型


发出信号的原因很多,这里按发出信号的原因简单分类,以了解各种信号:


(1) 与进程终止相关的信号。当进程退出,或者子进程终止时,发出这类信号。

(2) 与进程例外事件相关的信号。如进程越界,或企图写一个只读的内存区域(如程序正文区),或执行一个特权指令及其他各种硬件错误。

(3) 与在系统调用期间遇到不可恢复条件相关的信号。如执行系统调用exec时,原有资源已经释放,而目前系统资源又已经耗尽。

(4) 与执行系统调用时遇到非预测错误条件相关的信号。如执行一个并不存在的系统调用。

(5) 在用户态下的进程发出的信号。如进程调用系统调用kill向其他进程发送信号。

(6) 与终端交互相关的信号。如用户关闭一个终端,或按下break键等情况。

(7) 跟踪进程执行的信号。


Linux支持的信号列表如下。很多信号是与机器的体系结构相关的,首先列出的是POSIX.1中列出的信号:


信号 值 处理动作 发出信号的原因

----------------------------------------------------------------------

SIGHUP 1 A 终端挂起或者控制进程终止

SIGINT 2 A 键盘中断(如break键被按下)

SIGQUIT 3 C 键盘的退出键被按下

SIGILL 4 C 非法指令

SIGABRT 6 C 由abort(3)发出的退出指令

SIGFPE 8 C 浮点异常

SIGKILL 9 AEF Kill信号

SIGSEGV 11 C 无效的内存引用 


10)VFS

 硬链接:其实就是同一个文件具有多个别名,具有相同inode,而dentry不同。

              1. 文件具有相同的inode和data block;

              2. 只能对已存在的文件进行创建;

              3. 不同交叉文件系统进行硬链接的创建

              4. 不能对目录进行创建,只能对文件创建硬链接

              5. 删除一个硬链接并不影响其他具有相同inode号的文件;

软链接:软链接具有自己的inode,即具有自己的文件,只是这个文件中存放的内容是另一个文件的路径名。因此软链接具有自己的inode号以及用户数据块。

              1. 软链接有自己的文件属性及权限等;

              2. 软链接可以对不存在的文件或目录创建;

              3. 软链接可以交叉文件系统;

              4. 软链接可以对文件或目录创建;

              5. 创建软链接时,链接计数i_nlink不会增加;

              6. 删除软链接不会影响被指向的文件,但若指向的原文件被删除,则成死链接,但重新创建指向 的路径即可恢复为正常的软链接,只是源文件的内容可能变了。 

超级快:super block, dump2fs $df -h就能看到分区的超级快信息,是用于存储文件系统控制信息的数据结构,fs+block数目+inode数目,vfs是一个软件层,存在于内存。

例如cp ext2下的a文件到ext3下同下:

用户层read()->系统调用->sys_read->ext2文件系统下的读方法->物理介质->加载进内存->映射到b文件的内存地址->用ext3文件下系统下的写方法写到磁盘。


10)linux模块部署

   几个命令和文件:

    lsmod=cat/proc/modules

    modprobe -i <.ko> 安装模块, 根据modeules.dep里的依赖关系安装

    modprobe -r <.ko> 删除模块

    depmod:把新的驱动模块放到/lib/modules/内核/kernel/fs. net .sound,以后执行depmod,会生成相应模块的依赖关系,更新modules.dep文件。之后用上面命令安装

   开机模块加载以及硬件扫描信息保存在dmesg中。



以上是关于Linux内核学习总结的主要内容,如果未能解决你的问题,请参考以下文章

《Linux内核分析》第六周学习总结

《Linux内核分析》课程第一周学习总结

linux内核启动过程学习总结

Linux内核设计第三周学习总结 跟踪分析Linux内核的启动过程

LINUX内核分析第六周学习总结——进程的描述和进程的创建

《Linux内核设计与实现》课程学习重点问题总结