精华内容
下载资源
问答
  • Linux系统分析之线程

    万次阅读 热门讨论 2010-06-28 09:45:00
    什么是线程   Linux线程是一类特殊的进程,拥有各自的task_struct,内核并没有特别的调度算法和数据结构来表征线程,而仅仅是作为一个普通的进程,只是和其他进程共享进程空间。也就是说,如果程序...

    什么是线程

     

    Linux线程是一类特殊的进程,拥有各自的task_struct,内核并没有特别的调度算法和数据结构来表征线程,而仅仅是作为一个普通的进程,只是和其他进程共享进程空间。也就是说,如果程序运行于多线程环境,编写程序时必须检查一下项目:

    • 是否使用了不可重入的系统函数,例如字符串分割函数::strtok();
    • 是否已经对全局变量或静态变量进行了加锁;
    • 第三方库是否支持多线程。

     

    注:想要从核心获取线程id,应当使用current->pid。在核心里pid对进程而言是进程号,对于线程是线程号。同一进程的不同线程的pid是不同的,但同一进程的不同线程有统一的tgid,所以像getpid(),kill()等这种系统调用返回的都是tgid的值。

     

    线程的调度

     

    如果系统只有一个CPU,则它根本不可能真正同时进行一个以上的线程,它只能把CPU运行时间划分成若干个时间片,再将时间片分配给各个线程执行,在一个时间片的线程代码运行时,其它线程处于挂起状态,这种方式称之为并发(Concurrent)

     

    因为I/O操作速度远远慢于CPU处理速度,键鼠输入、硬盘读写、网络传输等速度远慢于内存读写速度,而内存速度又远慢于CPU缓存速度,所以有效的利用I/O操作间隙时的CPU,成为早期单核并发机制的来由。在并发环境下,CPU以”挂起->执行->挂起”的异步方式以很小的时间片分别运行各个线程,既让每个线程都得到合理的CPU资源,避免等待处理速度较慢的设备,又给用户以每个线程都在运行的错觉。在这种环境中,多线程程序真正改善的是系统的响应性能和程序的友好性。

     

    内核线程

     

    内核线程是独立运行在内核空间的标准进程,它和普通进程的区别是内核线程没有独立的地址空间,它的代码段和数据都在内核空间里,但可以被调度,被抢占。

     

    内核线程可以创建新的内核线程。一般情况下,内核线程会将它在创建时得到的函数永远执行下去,这个函数通常由一个循环构成(内核线程0)。在需要的时候,这个内核线程被唤醒,和执行,在完成任务以后,它会自动休眠。

     

    注:可以把内核想象成有很多内核线程在内核空间上同时运行的集合体。

     

    内核线程0

     

    内核线程0是在系统初始化阶段由start_kernel()函数从无到有创建的一个内核线程,创建的内容包括进程描述符、内核态堆栈、mm、fs、files、signals等。内核线程0最后的初始化工作是创建init内核线程,此后运行cpu_idle,线程0成为idle线程。

     

    进程1

     

    当调度程序选择到init线程时,init线程开始执行init()函数。init()函数最后调用execve()系统调用装入可执行程序init。自此,init内核线程变成一个普通的进程,即init进程,进程号为1,是其他所有用户进程的父进程。init进程从不终止,因为它创建和监控操作系统外层的所有进程的活动。

     

     

    展开全文
  • Linux系统分析之启动流程

    万次阅读 2010-06-07 09:31:00
      第一部分:内核的引导(核内引导) 启动设备使用lilo或grub等引导程序开始引导Linux系统,当引导程序成功完成引导任务后,Linux从它们手中接管了CPU的控制权, 然后CPU就开始执行Linux的核心映象...

     

    第一部分:内核的引导(核内引导)

    启动设备使用lilo或grub等引导程序开始引导Linux系统,当引导程序成功完成引导任务后,Linux从它们手中接管了CPU的控制权, 然后CPU就开始执行Linux的核心映象代码,开始了Linux启动过程。这里使用了几个汇编程序来引导Linux,这一步涉及到Linux源代码树中的“arch/i386/boot”下的这几个文件:bootsect.S、setup.S、video.S等。

     

    其中bootsect.S是生成引导扇区的汇编源码,它完成加载动作后直接跳转到setup.S的程序入口。setup.S的主要功能就是将系统参数(包括内存、磁盘等,由BIOS返回)拷贝到特别内存中,以便以后这些参数被保护模式下的代码来读取。此外,setup.S还将video.S中的代码包含进来,检测和设置显示器和显示模式。最后,setup.S将系统转换到保护模式,并跳转到 0x100000。

     

    0x100000这个内存地址存放的是解压后的内核,因为RedHat提供的内核包含了众多驱动和功能而显得比较大,所以在内核编译中使用了“makebzImage”方式,从而生成压缩过的内核,在RedHat中内核常常被命名为vmlinuz(也有类似initrd-2.6.11.12.img这样的命名)。在Linux的最初引导过程中,是通过"arch/i386/boot/compressed/"中的head.S利用misc.c中定义的decompress_kernel()函数,将内核vmlinuz解压到0x100000的。

     

    当CPU跳到0x100000时,将执行"arch/i386/kernel/head.S"中的 startup_32,它也是vmlinux的入口,然后就跳转到start_kernel()中去了。start_kernel() 是"init/main.c"中的定义的函数,start_kernel()中调用了一系列初始化函数,以完成kernel本身的设置。如果顺利执行完start_kernel(),则基本的Linux核心环境已经建立起来了。

     

    在start_kernel()的最后,通过调用kernelthread()函数,系统创建第一个核心线程init(),自身调用cpu_idle(NULL)休息。核心线程init()主要是来进行一些外设初始化的工作的,包括缓存区管理初始化(fs/buffer.c),虚拟内存管理初始化(mm/vmscan.c),文件系统初始化(fs/filesystems.c)和root文件系统的安装,调用do_basic_setup()完成外设及其驱动程序的初始化。

     

    当do_basic_setup()函数返回init(),init()又打开了/dev/console设备,重定向三个标准的输入输出文件stdin、stdout和stderr到控制台,最后,搜索文件系统中的init程序(或者由init=命令行参数指定的程序),并使用execve()系统调用加载执行init程序。到此init()函数结束,init进程从内核态切换到用户态(进程号1),内核的引导部分也到此结束了。

     

    第二部分:运行init

    init的进程号是1,是系统所有进程的起点,Linux在完成核内引导以后,就开始运行init程序(init()的最后一行)。init程序读取并执行配置文件/etc/inittab。

     

    第三部分:系统初始化

    在init的配置文件中有这么一行:

    si::sysinit:/etc/rc.d/rc.sysinit

    它调用执行了/etc/rc.d/rc.sysinit,而rc.sysinit是一个bash shell的脚本,它主要是完成一些系统初始化的工作,rc.sysinit是每一个运行级别都要首先运行的重要脚本。它主要完成的工作有:激活交换分区,检查磁盘,加载硬件模块以及其它一些需要优先执行任务。

     

    第四部分:启动对应运行级别的守护进程

    在rc.sysinit执行后,将返回init继续其它的动作,通常接下来会执行到/etc/rc.d/rc程序。以运行级别3为例,init将执行配置文件inittab中的以下这行:

    l5:5:wait:/etc/rc.d/rc 5

     

    这一行表示以5为参数运行/etc/rc.d/rc,/etc/rc.d/rc是一个Shell脚本,它接受数字5作为参数,去执行/etc/rc.d/rc5.d/目录下的所有的rc启动脚本。当然,/etc/rc.d/rc5.d/目录中的这些启动脚本实际上都是一些链接文件,而不是真正的rc启动脚本,真正的rc启动脚本实际上都是放在/etc/rc.d/init.d/目录下。

     

    第五部分:建立终端

    rc执行完毕后,返回init。这时基本系统环境已经设置好了,各种守护进程也已经启动了。init接下来会打开6个终端(定义在/etc/inittab中),以便用户登录系统。通过按Alt+Fn(n对应1-6)可以在这6个终端中切换。

     

    第六部分:登录系统,启动完成

    Login程序会接收终端程序传来的用户名作为用户名参数,并进行分析。如果用户名不是root,且存在/etc/nologin文件,login将输出nologin文件的内容,然后退出。这通常用来系统维护时防止非root用户登录。

     

    只有/etc/securetty中登记了的终端才允许root用户登录,如果不存在这个文件,则root可以在任何终端上登录。/etc/usertty文件用于对用户作出附加访问限制,如果不存在这个文件,则没有其他限制。

     

    在分析完用户名后,login将搜索/etc/passwd以及/etc/shadow来验证密码以及设置账户的其它信息,比如:主目录是什么、使用何种shell。如果没有指定主目录,将默认为根目录;如果没有指定shell,将默认为/bin/bash。

     

    login程序成功后,会向对应的终端在输出最近一次登录的信息(在/var/log/lastlog中有记录),并检查用户是否有新邮件(在/usr/spool/mail/的对应用户名目录下)。然后开始设置各种环境变量:对于bash来说,系统首先寻找/etc/profile脚本文件,并执行它;然后如果用户的主目录中存在.bash_profile文件,就执行它。

     

    展开全文
  • 经过这一段时间的学习,自己对linux也有了一定的认识,今天这篇博客对以往的知识进行一个总结吧。

    经过这一段时间的学习,自己对linux也有了一定的认识,今天这篇博客对以往的知识进行一个总结吧。

    以往linux学习的博客,从上而下是学习深入的过程,我的博客链接如下:

    第一篇:《Linux操作系统分析》之分析计算机如何启动以及如何工作运行

    第二篇:《Linux操作系统分析》之分析精简的Linux的内核中断和时间片轮询

    第三篇:《Linux操作系统分析》之跟踪分析Linux内核的启动过程

    第四篇:《Linux操作系统分析》之使用库函数API和C代码中嵌入汇编代码两种方式使用同一个系统调用

    第五篇:《Linux操作系统分析》之分析系统调用system_call的处理过程

    第六篇:《Linux操作系统分析》之分析Linux内核创建一个新进程的过程

    第七篇:《Linux操作系统分析》之分析Linux内核如何装载和启动一个可执行程序

    第八篇:《Linux操作系统分析》之理解进程调度时机跟踪分析进程调度与进程切换的过程


    任何形式的学习都不是一个一蹴而就的过程,要经历一段痛苦的磨练期。

    在接触linux开始,各种命令繁多,不过经过一段时间自己也可以记得下来。关于命令,我会写另一篇博客将我学习鸟哥的私房菜的一些基本命令做个总结。

    言归正传,现在对我学习深入理解linux内核这本书,做一个阶段性的总结。

    我将对linux的以下方面做个总结。包括计算机的启动、内核加载、进程、中断和异常、进程调度、系统调用、程序的执行。其他的部分在接下来学习完成后在进行分析和总结。

    一、计算机的启动

    正如我linux第一篇博客所言,我们会好奇计算机是怎么启动的。如何从一个铁疙瘩变成一个计算速度秒杀人类的机器那。我在第一篇博客中借用了一句话:"pull oneself up by one's bootstraps"字面意思是”拽着鞋带把自己拉起来”。这个过程想必大家也不能做到。其实计算机的启动之初我们需要第一条指令,然后一直等待输入指令。那么第一条指令是什么时候输入的那。在BIOS中电脑在固定位置被写入了第一条指令。当第一条指令加载后,就可以执行后续的行为。

    简单的来说计算机的运行,就是一个人一直等待着做事情,有事情了他就去做,没事情了,就自己发呆。如果有多个事情同时来了。这个人就要根据自己的需要判断先后执行的顺序。

    详细的分析见我的第一篇博客。

    二、内核加载

    内核加载的流程大概如下:执行asmlinkage __visible void __init start_kernel(void)这个函数,在其中的有一个set_task_stack_end_magic(&init_task);函数,这个函数中该结构体(init_task)在linux启动时被设置为current_task。(此时idle进程已经启动)然后对其他的信息也进行初始化。接着进行到了rest_init();这个地方。当初始化到rest_init函数中时调用kernel_thread(kernel_init, NULL, CLONE_FS);函数启动第一个内核线程kernel_init。由kernel_init再通过do_execve启动/sbin/init。这就是我们看到的init进程,进程号为1。初始化完成后linux调用scheule整个系统就运行起来了。

    所以我们可以看出来:idle是一个进程,其pid为0。是Linux引导中创建的第一个进程,完成加载系统后,演变为进程调度、交换及存储管理进程。主处理器上的idle由原始进程(pid=0)演变而来。从处理器上的idle由init进程fork得到,但是它们的pid都为0。Idle进程为最低优先级,且不参与调度,只是在运行队列为空的时候才被调度。Idle循环等待need_resched置位。1号进程是init 进程,由0进程创建,完成系统的初始化. 是系统中所有其它用户进程的祖先进程

    详细的分析见我的第三篇博客。

    三、进程。

    通常来讲进程就是程序执行时的一个实例,可以把它看作是充分描述程序已经执行到何种程度的数据结构的汇集。

    进程和人类相似:它们会被创建,而后有或多或少的有效的生命,可以产生一个或者多个子进程,最终都是要死亡的。当然程序没有性别,只有一个父亲。

    我们从内核的观点来看的话:进程的目的就是担当分配系统资源的实体。

    一个进程可以和其他的进程共享代码,但是它们有各自独立的数据拷贝(栈和堆),相互之间不影响对方。

    每个进程都有自己的进程描述符(task_struct类型结构),在这里面有:进程的基本信息、指向内存区描述符的指针、与进程相关的tty、当前目录、指向文件描述符的指针、所接受的信号等等。

    进程有以下的状态:可运行状态、可中断的等待状态、不可中断的等待状态、暂停状态、跟踪状态。还有两个进程的状态既可以存放在state字段中,也可以放在exit_state字段中。只有当进程的执行被终止的时,进程的状态才会变为这两种状态中的一种。即:僵死状态、僵死撤销状态。

    当然每个进程都有一个唯一的进程描述符(process ID)。

    进程切换:为了控制进程的执行,内核必须有能力挂起正在cpu上运行的进程,并恢复以前挂起的某个进程的执行。详细的分析见我的第八篇博客。这里我们简单的说,用人来为例说明,进程的切换就是说:一个人在做一件事情A的时候,发生了另一件事情B。此时他需要去处理另一件事情B。这时候他要切换两个事情了,首先他要知道做完B事情后继续做A事情的话,应该从哪里做起,这就要保存A事情执行的信息了。其次他要知道B事情从哪里开始,去哪里找工具,这些信息需要读入了。当这个保存和读入的过程完成,他就可以去做B事情,这就完成了进程的切换。

    关于这章我有以下的总结:

    一、对于每个进程来说,Linux都把两个不同的数据结构紧凑地存放在一个单独为进程分配的存储区域内:一个是内核态的进程堆栈,另一个是紧挨着进程描述符的小数据节后thread_info,叫做线程描述符。(数据类型是union,通常是8192字节即两个页框)。线程描述符驻留于这个内存区的开始,而栈从末端(高地址)开始增长。因为thread_info是52字节长,所以内核栈能扩展到8140字节。

    二、当一个进程创建时,它与父进程相同。它接受父进程地址空间的一个(逻辑)拷贝,并从进程创建系统调用的下一条指令开始执行与父进程相同的代码。尽管父子进程可以共享程序代码的页,但是它们各自有独立的数据拷贝(栈和堆),因此子进程对一个内存单元的修改对父进程是不可见的(反之亦然)。
    三、进程的创建过程大概就是这样的:sys_clone—>do_fork—>copy_process—>dup_task_struct—>copy_thread—>ret_from_fork。

    详细的分析见我的第六篇博客。

    四、中断和异常

    详细的分析见我的第二篇博客。

    中断简单的来说就是在你做一件事的时候,被打断去执行其他的事情。举个日常生活中的例子,比如说我正在厨房用煤气烧一壶水,这样就只能守在厨房里,苦苦等着水开——如果水溢出来浇灭了煤气,有可能就要发生一场灾难了。等啊等啊,外边突然传来了惊奇的叫声“怎么不关水龙头?”于是我惭愧的发现,刚才接水之后只顾着抱怨这份无聊的差事,居然忘了这事,于是慌慌张张的冲向水管,三下两下关了龙头,声音又传到耳边,“怎么干什么都是这么马虎?”。伸伸舌头,这件小事就这么过去了,我落寞的眼神又落在了水壶上。门外忽然又传来了铿锵有力的歌声,我最喜欢的古装剧要开演了,真想夺门而出,然而,听着水壶发出“咕嘟咕嘟”的声音,我清楚:除非等到水开,否则没有我享受人生的时候。

    中断有两种:

    同步中断:当指令执行时由CPU控制单元产生的,之所以成为同步,是因为只有在一条指令终止执行后CPU才会发出中断。

    异步中断:由其他硬件设备依照CPU始终信号随机产生的。

    中断信号:提供一种特殊的方式,使处理器转而去运行正常控制流之外的代码。当一个中断信号达到时,CPU必须停止它当前正在做的事情,并且切换到一个新的活动。(这就需要在内核堆栈保存程序计数器的当前值,即eip和cs寄存器的内容,并且要把与中断类型相关的一个地址放进程序计算器)

    中断和异常的分类:

    中断:可屏蔽中断、非屏蔽中断。

    异常:

    处理器探测异常:故障、陷阱、异常中止。

    编程异常(也叫软中断):在编程者发出请求时发生,由int或int3指令触发。

    五、进程调度

    详细的分析见我的第八篇博客。

    简单的来说,进程的调度涉及到以下过程:schedule()函数选择一个新的进程来运行,并调用context_switch进行上下文的切换,这个宏调用switch_to来进行关键上下文切换 
    next = pick_next_task(rq, prev);   进程调度算法都封装这个函数内部,通过这个函数选出一个进程作为下一个执行的进行。
    context_switch(rq, prev, next);  进程上下文切换
    switch_to利用了prev和next两个参数:prev指向当前进程,next指向被调度的进程,来进行进程的切换。

    在学习了这段后,我有以下总结:

    一、进程调度的时机
    1、中断处理过程(包括时钟中断、I/O中断、系统调用和异常)中,直接调用schedule(),或者返回用户态时根据need_resched标记调用schedule();
    2、内核线程可以直接调用schedule()进行进程切换,也可以在中断处理过程中进行调度,也就是说内核线程作为一类的特殊的进程可以主动调度,也可以被动调度;
    3、用户态进程无法实现主动调度,仅能通过陷入内核态后的某个时机点进行调度,即在中断处理过程中进行调度。
    二、进程的切换
    1、为了控制进程的执行,内核必须有能力挂起正在CPU上执行的进程,并恢复以前挂起的某个进程的执行,这叫做进程切换、任务切换、上下文切换;
    2、挂起正在CPU上执行的进程,与中断时保存现场是不同的,中断前后是在同一个进程上下文中,只是由用户态转向内核态执行;
    3、进程上下文包含了进程执行需要的所有信息
    3.1、用户地址空间: 包括程序代码,数据,用户堆栈等
    3.2、控制信息 :进程描述符,内核堆栈等
    3.3、硬件上下文(注意中断也要保存硬件上下文只是保存的方法不同)
    4、进程切换执行的流行
     schedule() -> pick_next_task()->context_switch() -> switch_to -> __switch_to()
    三、几种特殊情况
    1、通过中断处理过程中的调度时机,用户态进程与内核线程之间互相切换和内核线程之间互相切换,与最一般的情况非常类似,只是内核线程运行过程中发生中断没有进程用户态和内核态的转换;
    2、内核线程主动调用schedule(),只有进程上下文的切换,没有发生中断上下文的切换,与最一般的情况略简略;
    3、创建子进程的系统调用在子进程中的执行起点及返回用户态,如fork。(对应switch_to流程中的第6步,此时返回到ret_from_fork)
    4、加载一个新的可执行程序后返回到用户态的情况,如execve。(对应switch_to流程中的第6步,此时静态链接返回到程序的start处,动态链接则返回到动态链接器处)

    六、系统调用

    详细的分析见我的第四篇和第五篇博客。

    Linux中是通过寄存器%eax传递系统调用号,所以具体调用fork的过程是:将20存入%eax中,然后进行系统调用.对于参数传递,Linux是通过寄存器完成的。Linux最多允许向系统调用传递6个参数,分别依次由%ebx,%ecx,%edx,%esi,%edi和%ebp这个6个寄存器完成。进程运行在用户态和内核态时使用不同的栈,分别叫做用户栈和内核栈。两者各自负责相应特权级别状态下的函数调用。系统调用时,进程不仅是用户态到内核态的切换,同时也要切换栈,这样内核态的系统调用才能在内核栈上完成调用。系统调用返回时,还要切换回用户栈,继续完成用户态下的函数调用。

    我们也可以看到system_call的过程如下:

    1)系统调用的初始化的顺序是:start_kernel()->trap_init()->set_system_trap_gates(SYSCALL_VECTOR,&system_call);
    2)用户态到内核态通过0x80进行中断,在内核初始化期间调用trap_init(),用函数set_system_trap_gates(),建立了对应于向量128的中断描述符表表项,从而进入相应的中断服务。
    3)system_call()函数首先将系统调用号或中断处理程序需要用到的所有的CPU寄存器保存到相应的栈中。然后进行服务的处理。当系统调用服务例程结束时,system_call()函数从eax获得它的返回值。然后进行一系列的检查,最后恢复用户态进程的执行。

    七、程序的执行

    详细的分析见我的第七篇博客。

    对于程序的执行有以下的总结:

    一、可执行程序的装载是一个系统调用。可执行程序执行时,由execve系统调用后便陷入到内核态里,而后加载可执行文件,把当前进程的可执行程序覆盖掉,当execve系统调用返回的时,返回的则是新的可执行程序(执行起点main处)。
     二、execve函数在当前进程的上下文中加载并运行一个新的程序。它会覆盖当前进程的地址空间,但是并没有创建一个新的进程。新的程序仍然有相同的PID,并且继承了调用execve函数时已打开的所有的文件描述符。
    三、execve函数加载并运行可执行目标文件filename,且带参数列表argv和环境变量列表envp。argv变量指向一个以null结尾的指针数组,其中每个指针都指向一个参数串即可执行目标的名字。envp变量结构与argv变量一样,不同的是每个环境变量串是形如:“NAME=VALUE”的名字-值对。
    四、execve函数与fork函数的差异:只有当出现错误的时候,execve才会返回到调用程序。也就是说execve系统调用和fork系统调用的区别是前者成功不返回,不成功返回-1,后者是返回两次。

    总结:

    经过了一段时间的学习,自己对linux也有了一定的认识。

    也经历了看山是山,看山不是山,看山还是山的过程。最后还是觉得学的越多,自己知道的越少。

    不过庆幸的是我收获了相关的知识,也加强了自己克服困难的能力。

    当然在这过程中也有一定的遗憾,那就是学习的枯燥导致自己有时候对自己的要求放松,没有做到持之以恒。

    备注:
    杨峻鹏 + 原创作品转载请注明出处 + 《Linux内核分析》MOOC课程http://mooc.study.163.com/course/USTC-1000029000 


    展开全文
  • linux内核分析之文件系统

    千次阅读 2016-04-22 00:00:44
    linux内核分析之文件系统linux内核分析之文件系统 一文件系统的安装和卸载分析 1 文件系统的安装 11 总领提纲 12 代码分析 13 实例考察path_walk 2 文件系统的卸载 21 提纲 22 代码分析 二问答归纳 1 问题 2 回答 三...

    linux内核分析之文件系统

    一、文件系统的安装和卸载分析

    1.1 文件系统的安装

    1.1.1 总领提纲

    文件系统的安装过程中,有几个重要的数据结构:

    • file_system_type : 这个数据结构是VFS进入具体文件系统的一个转折点,因为该文件系统中有一个函数指针read_super,这个函指针用于将设备上的super_block读入内存,并且建立起VFS的super_block。该结构只是用于在安装时说明”如何安装该文件系统”的,并不作为被安装到安装节点的内容部分。
    • vfsmount : 这个数据结构记录了目录节点上安装的文件系统详细信息。
    • super_block : 这个数据结构可以说是一个文件系统对象实例,一旦某个目录节点与之关联起来,就可以说这个目录节点上挂载了该文件系统。

    另外,在安装文件系统之前,从设备文件层面看,一个块设备是可以被访问的。只不过这时候访问时只能当作一个特殊文件来读写,也就是说只是一个大的线性空间,或者说这时候的块设备只是一个容量极大的文件而已。但是安装了文件系统之后,整个文件系统作为块设备的代理,通过目录节点访问,这时候对块设备的管理就可以按照一定格式来管理,读写也是按照一定格式来读写,在文件系统的管理之下,块设备的灵活性极大。

    1.1.2 代码分析

    文件系统的安装工作主要由do_mount函数完成。
    [fs/super.c : 1338~1445 : do_mount]

    1338 long do_mount(char * dev_name, char * dir_name, char *type_page,
    1339                   unsigned long flags, void *data_page)
    1340 {
    1341         struct file_system_type * fstype;
    1342         struct nameidata nd;
    1343         struct vfsmount *mnt = NULL;
    1344         struct super_block *sb;
    1345         int retval = 0;
    1346         
                 /* .......此处省略代码若干  */
    1385         /* ... filesystem driver... */
    1386         fstype = get_fs_type(type_page);
    1387         if (!fstype)
    1388                 return -ENODEV;
    1389 
    1390         /* ... and mountpoint. Do the lookup first to force automounting. */
    1391         if (path_init(dir_name,
    1392                       LOOKUP_FOLLOW|LOOKUP_POSITIVE|LOOKUP_DIRECTORY, &nd))
    1393                 retval = path_walk(dir_name, &nd);
    1394         if (retval)
    1395                 goto fs_out;
    1396 
    1397         /* get superblock, locks mount_sem on success */
    1398         if (fstype->fs_flags & FS_NOMOUNT)
    1399                 sb = ERR_PTR(-EINVAL);
    1400         else if (fstype->fs_flags & FS_REQUIRES_DEV)
    1401                 sb = get_sb_bdev(fstype, dev_name, flags, data_page);
    1402         else if (fstype->fs_flags & FS_SINGLE)
    1403                 sb = get_sb_single(fstype, flags, data_page);
    1404         else
    1405                 sb = get_sb_nodev(fstype, flags, data_page);
    1406 
                 /*......此处省略代码若干*/
    1414 
    1415         /* Refuse the same filesystem on the same mount point */
    1416         retval = -EBUSY;
    1417         if (nd.mnt && nd.mnt->mnt_sb == sb
    1418                    && nd.mnt->mnt_root == nd.dentry)
    1419                 goto fail;
    1420 
    1421         retval = -ENOENT;
    1422         if (!nd.dentry->d_inode)
    1423                 goto fail;
    1424         down(&nd.dentry->d_inode->i_zombie);
    1425         if (!IS_DEADDIR(nd.dentry->d_inode)) {
    1426                 retval = -ENOMEM;
    1427                 mnt = add_vfsmnt(&nd, sb->s_root, dev_name);
    1428         }
    1429         up(&nd.dentry->d_inode->i_zombie);
    1430         if (!mnt)
    1431                 goto fail;
    1432         retval = 0;
                 /*......此处省略代码若干*/
    1445 }

    第一步:查找file_system_type
    1386: 系统支持的每种文件系统都有一个file_system_type结构用于描述和记录文件系统的一些特性。file_system_type定义于[include/linux/fs.h : 839~846],该结构在系统启动,或者是文件系统作为模块被加载进入内核时注册到一全局链表上。这里通过遍历该链表并且比对字符串从而查找到文件系统对应的file_system_type结构。

    第二步:查找安装点的dentry结构。
    1391~1393: 查找安装点的dentry结构,返回的nd.dentry即为根据路径名查找到的安装节点dentry。而nd.mnt是目前安装节点所在的文件系统的安装信息。

    第三步:将待安装文件系统的super_block读取进内存并且建立起VFS的super_block。
    1401 : 考虑非特殊文件系统的情况,调用get_sb_bdev读取超级块。该函数定义于[fs/super.c : 785~847 : get_sb_bdev]

    第四步:开始安装,连接dentry—>vfsmount—>super_block
    调用add_vfsmnt函数进行文件系统的安装,实际上是利用vfsmount结构将安装节点和super_block关联起来。调用完后dentry->d_vfsmnt链表上挂接有vfsmount,vfsmount->mnt_mountpoint = dentry,vfsmount->mnt_root = super_block->s_root。dentry、vfsmount、super_block如图所示:

    (链接后的dentry、vfsmount、super_block之间的关系,详情请参考这篇文章)

    1.1.3 实例考察path_walk

    当一个目录节点/home/User/dir被挂接了一个设备时,当利用路径查找/dir下文件时会是如何的呢?path_walk相关部分如下。
    [fs/namei.c : 501~511 : path_walk]

     501                 dentry = cached_lookup(nd->dentry, &this, LOOKUP_CONTINUE);
     502                 if (!dentry) {
     503                         dentry = real_lookup(nd->dentry, &this, LOOKUP_CONTINUE);
     504                         err = PTR_ERR(dentry);
     505                         if (IS_ERR(dentry))
     506                                 break;
     507                 }
     508                 /* Check mountpoints.. */
     509                 /*如果该节点是一个挂载节点,则前进到挂载设备的根目录中去*/
     510                 while (d_mountpoint(dentry) && __follow_down(&nd->mnt, &dentry))
     511                         ;

    510 : 调用d_mountpoint检查该节点是不是挂载节点,如果是挂载节点就进入被挂载设备的根节点中,用挂载设备的根节点替代这次查找得到的dentry。逻辑上与该语句等效dentry = nd->mnt->mnt_root。d_mountpoint和__follow_down分别定义于[include/linux/dcache.h : 261][fs/namei.c : 352]

    [include/linux/dcache.h : 261]
    261 static __inline__ int d_mountpoint(struct dentry *dentry)
    262 { 
    263         return !list_empty(&dentry->d_vfsmnt);
    264 }
    
    [fs/namei.c : 352]
     352 static inline int __follow_down(struct vfsmount **mnt, struct dentry **dentry)
     353 { 
     354         struct list_head *p;
     355         spin_lock(&dcache_lock);
     356         p = (*dentry)->d_vfsmnt.next;
     357         while (p != &(*dentry)->d_vfsmnt) {
     358                 struct vfsmount *tmp;
     359                 tmp = list_entry(p, struct vfsmount, mnt_clash);
     360                 if (tmp->mnt_parent == *mnt) {
     361                         *mnt = mntget(tmp);
     362                         spin_unlock(&dcache_lock);
     363                         mntput(tmp->mnt_parent);
     364                         /* tmp holds the mountpoint, so... */
     365                         dput(*dentry);
     366                         *dentry = dget(tmp->mnt_root);
     367                         return 1;
     368                 }
     369                 p = p->next;
     370         }
     371         spin_unlock(&dcache_lock);
     372         return 0;
     373 }
    

    1.2 文件系统的卸载

    1.2.1 提纲

    文件系统的卸载主要是拆除dentry、vfsmount、super_block之间的链接关系。在文件系统卸载时,完成了以下工作:

    • 将vfsmount从相关的list中移除
    • 将所有文件系统的inode、super_block、数据块会写到设备(sync)
    • 释放所有该文件系统的dentry

    1.2.2 代码分析

    文件系统的卸载,主要由do_umount函数完成。
    [fs/super.c : 1045]

    1045 static int do_umount(struct vfsmount *mnt, int umount_root, int flags)
    1046 {
    1047 /* 入口参数:
    1048  *      vfsmount *mnt : 卸载点上的安装信息结构,在安装文件系统时,该结构被填充。
    1049  *      umount_root : 为0时表示当前要卸载的不是根目录,为非0时,表示当前要卸载根目录
    1050  *
    1051  * */
    1052 
    1053         struct super_block * sb = mnt->mnt_sb;
                 /*......此处省略代码若干*/
    1065         //如果当前要卸载的mnt挂载点是根目录,则进行只读重装
    1066         if (mnt == current->fs->rootmnt && !umount_root) {
    1067                 int retval = 0;
    1068                 /*
    1069                  * Special case for "unmounting" root ...
    1070                  * we just try to remount it readonly.
    1071                  */
    1072                 mntput(mnt);
    1073                 if (!(sb->s_flags & MS_RDONLY))
    1074                         retval = do_remount_sb(sb, MS_RDONLY, 0);
    1075                 return retval;
    1076         }
    1077 
    1078         spin_lock(&dcache_lock);
    1079 
    1080         /*
    1081          * 如果该文件系统被安装多次,并且引用技术大于2 ,说明还有其它的目录
    1082          * 节点挂载了该文件系统,这时只需要移除vfsmount即可,不做回收工作
    1083          * */
    1084         if (mnt->mnt_instances.next != mnt->mnt_instances.prev) {
    1085                 if (atomic_read(&mnt->mnt_count) > 2) {
    1086                         spin_unlock(&dcache_lock);
    1087                         mntput(mnt);
    1088                         return -EBUSY;
    1089                 }
    1090                 if (sb->s_type->fs_flags & FS_SINGLE)
    1091                         put_filesystem(sb->s_type);
    1092                 /* We hold two references, so mntput() is safe */
    1093                 mntput(mnt);
    1094                 /*移除vfsmount*/
    1095                 remove_vfsmnt(mnt);
    1096                 return 0;
    1097         }
                 /*......此处省略代码若干*/
    1130 
    1131         /*
    1132          * 注:当一个dentry在内存中建立起来之后,每当被使用一次,
    1133          *     则其引用计数加1,被用完之后引用计数减1直到
    1134          *     一个dentry引用计数变为0之后,说明该当前已经
    1135          *     没有进程使用该dentry了,但根据程序的局部性
    1136          *     原理,该dentry不会被马上释放掉,而是被链入
    1137          *     由LRU管理的unused_list队列当中,因为它很可能又会
    1138          *     被再次使用,它会一直在该队列中直到被再次使用或者
    1139          *     被LRU回收。当文件系统被卸载时,所有属于该
    1140          *     文件系统的dentry都会被立即回收,而不会等到被LRU回收。
    1141          * */
    1142         //释放所有ununed_list中的dentry结构。
    1143         shrink_dcache_sb(sb);
    1144         //立即将内容回写到设备
    1145         fsync_dev(sb->s_dev);
    1146         if (sb->s_root->d_inode->i_state) {
    1147                 mntput(mnt);
    1148                 return -EBUSY;
    1149         }
                /*......此处成略代码若干*/
    1162         remove_vfsmnt(mnt);
    1163 
    1164         kill_super(sb, umount_root);
    1165         return 0;
    1166 }

    第一步: 将vfsmount从相关的list中移除
    [fs/super.c : 411~428 : remove_vfsmnt]

     411 static void remove_vfsmnt(struct vfsmount *mnt)
     412 {
     413         /* First of all, remove it from all lists */
     414         list_del(&mnt->mnt_instances);
     415         list_del(&mnt->mnt_clash);
     416         list_del(&mnt->mnt_list);
     417         list_del(&mnt->mnt_child);
     418         spin_unlock(&dcache_lock);
                 /*.....此处省略代码若干*/
     428 }             

    414:将vsfmount从该文件系统super_block->s_mounts维护的vsfmount链表中删除。
    415:将vsfmount从该安装节点dentry->d_vfsmnt维护的vsfmount链表中删除。
    416:将vsfmount从其父设备,即上一层目录的文件系统维护的mnt_child链表中删除
    417:将vsfmount从内核维护的全局mnt_list链表中删除。

    第二步: 释放所有该文件系统的dentry
    [fs/super.c : 1103]

    1103         shrink_dcache_sb(sb);

    第三步: 将该文件系统的super_block以及所有的inode会写到设备内。

    1105         fsync_dev(sb->s_dev);

    二、问答归纳

    2.1 问题:

    • (1) 文件系统安装意味这什么?
    • (2) 文件系统的安装完成了哪些工作?
    • (3) 文件系统的卸载完成了哪些工作
    • (4) 在某个目录节点安装了一个文件系统后,super_block和vfsmount与dentry关联起来的作用是什么?

    2.2 回答:

    • (1) 意味着安装节点dentry与一个vfsmount关联起来了,并且vfsmount与一个具体文件系统的super_block关联起来。
    • (2) 安装完成的工作有:
      • 获取file_system_type
      • 获取安装节点的dentry
      • 获取被安装文件系统的super_block*(通过file_system_type->super_block读入)*
      • 关联dentry、vfsmount、super_block
    • (3) 卸载完成的工作有:
      • 将vfsmount从相关的list中移除
      • 将所有文件系统的inode、super_block、数据块会写到设备(sync)
      • 释放所有该文件系统的dentry
    • (4) 一旦某个目录节点的dentry与一个文件系统的vfsmount关联起来之后,以后访问该节点时,就会检测到其与vfsmount相关联,检测到该节点与一个vfsmount已经关联之后,就会自动转入vfsmount->mnt_root内进行访问,而不是访问原dentry的内容。

    三、文件的打开

    3.1打开文件的本质

    文件的打开,本质上是建立起进程与文件之间的链接,也就是file结构,并且返回file结构的索引fd。使得进程能够通过file结构来对文件进行访问。

    3.2打开文件的过程

    • (1) 从进程的task_struct中分配一个未使用的fd
    • (2) 通过路径名查找或者创建一个dentry
      • 通过path_init、path_walk查找路径名对应的dentry
      • 如果查找不到该路径名的dentry,并且设置了CREATE标志,则在路径名最后一个目录下创建该文件。
    • (3) 打开dentry,返回一个建立号的file结构
      • 分配一个空闲的file结构
      • 填充file中的VFS层信息,在这里复制了dentry->inode->i_fop
      • 调用inode->i_fop打开设备
      • 返回file结构

    函数调用链为:
    [fs/open.c : 746 : sys_open]:

    四、文件创建

    在1.2的分析中,我们知道,在open_namei函数中,一旦查找路径名对应的dentry不存在的时候,就要在路径名最后一个目录下创建该文件。现在来分析文件创建的过程。

    4.1 文件创建的过程

    • (1) 调用lookup_hash在父目录下搜索dentry,考虑到这里是创建文件的情况这里会搜索不到,所以在内存中分配一个dentry
    • (2) 调用vfs_create创建文件
      • 调用父目录的dentry->inode->i_op->create创建一个文件。
        • dentry->inode->i_op->create创建一个文件
        • 调用ext2_new_inode创建inode
          • 调用 new_inode在内存中分配一个inode结构
          • 根据位图在设备上分配一个inode(实际上这里根据位图分配了一个inode节点号)
          • 填充部分inode信息,包括mode字段,fsuid等等
        • 填充VFS层inode信息,包括inode->i_op、inode->f_op
        • 调用ext2_add_entry将dentry和inode号对应起来并且写入父目录的数据块中

    4.2 关于文件创建的理解

    • 在目录parent下创建一个文件,这是因具体文件系统而异的,所以create具体由parent->inode->i_op->create来执行。在parent下创建文件,主要作的工作是分配inode号,然后使用这个inode号和文件名构成一个ext2_dir_entry_2结构写入parent的数据块中(由parent对应的inode号索引得到)。请注意,dentry和inode是VFS层的对象,它们仅存在于内存中,设备上存放的是ext2_dir_entry_2和ext2_inode。对于具体文件系统来说,访问和管理文件只需要inode,而dentry只是用于支持VFS层的。

    附注:分配inode的原则

    • 为了提高磁盘访问速度,所以将磁盘上同一盘面的块(扇区)被划分为一个块组来管理。并且另一方面,在创建一个文件时,它的inode和数据块应当也被分配到同一个块组内,因为inode和数据块的访问总是形影不离的。因此,,设备上的块组中存放的它的inode和数据块应当也被分配到同一个块组内,因为inode和数据块的访问总是形影不离的。因此,,设备上的块组中存放的ininode数量和数据块数量是成一定比例的。这个比例由文件平均大小得到的。比如说,系统上文件平均大小为fsizes,一个块组的总大小为gbsizes,一个数据块的大小为bsizes,则该块组能放下的文件数量为nf = gbsizes / fsizes。每个文件需要的数据块为nb = fsizes / bsizes。这样,inode和数据块的比例就为 nf : nb = gbsizes / bsizes : fsizes / bsizes。这样格式化了之后,在创建一个文件时,一旦块组内有空闲的inode的时候,很大概率上空闲的数据块也足以存放该inode对应的文件数据。
    • 当创建一个目录的时候,也希望该目录下的文件(目录下的子目录也是文件,看作一个文件来处理)都能处于同一个块组上,这样才能提高磁盘访问效率。同样的,每个目录下的文件数量也有一个统计值(系统平均值),假设为N,创建目录时,一旦发现块组内的空闲节点小于N,则认为该块组放不下一个目录,这时候需要寻找另一个块组进行创建。
    展开全文
  • linux 系统启动过程分析

    千次阅读 2014-07-18 16:36:28
    Linux系统的一般启动过程通常划分为内核引导、内核启动和应用程序启动3个阶段,如下图所示(摘自:ARM 嵌入式LINUX系统 开发 8.2节) 第一阶段是目标板硬件初始化,解压内核映像,再跳转到内核映像入口。这...
  • linux input 子系统分析

    千次阅读 2011-09-14 19:48:59
    linux input子系统分析--子系统核心.事件处理层.事件传递过程 一. 输入子系统核心分析。  1.输入子系统核心对应与/drivers/input/input.c文件,这个也是作为一个模块注册到内核的。所以首先分析模块初始化函数。...
  • linux系统日志以及分析

    万次阅读 多人点赞 2017-12-12 15:52:14
    Linux系统拥有非常灵活和强大的日志功能,可以保存几乎所有的操作记录,并可以从中检索出我们需要的信息。 大部分Linux发行版默认的日志守护进程为 syslog,位于 /etc/syslog 或 /etc/syslogd 或/etc/rsyslog.d...
  • Linux input子系统分析之一:软件层次

    千次阅读 2015-08-23 10:49:35
    输入输出是用户和产品交互的手段,因此输入驱动开发在Linux驱动开发中很常见。同时,input子系统的分层架构思想在Linux驱动设计中极具代表性和先进性,因此对Linux input子系统
  • linux内核配置系统分析

    千次阅读 2007-01-10 09:41:00
    随着 Linux 操作系统的广泛应用,特别是 Linux 在嵌入式领域的发展,越来越多的人开始投身到 Linux 内核级的开发中。面对日益庞大的 Linux 内核源代码,开发者在完成自己的内核代码后,都将面临着同样的问题,即如何...
  • Linux系统故障分析与排查

    万次阅读 2016-04-11 15:23:51
    熟悉Linux系统的日志管理,了解常见故障的分析与解决办法,将有助于管理员快速定位故障点。“对症下药”及时解决各种系统问题。 日志分析及管理 日志文件是用于记录Linux系统中各种运行消息的文件,相当于Linux...
  • linux文件系统的系统分析--开篇

    千次阅读 2012-04-02 19:13:14
    在看设备模型的时候,对sysfs、devtmpfs有一定了解,也分析linux下的一个简单的文件系统:omfs。但当时的分析比较散,现在觉得还是得花时间再系统的整理一下为好。因为目前对linux的内存管理还不熟悉,关于vfs中...
  • 但是在linux系统中,I2C子系统结构是比较复杂的,因为它涉及到很多linux内核相关的知识,理解起来十分费劲。(一)I2C架构概述Linux 的I2C体系架构分为3个组成部分:(1)I2C核心:I2C核心提供了I2C总线驱动和...
  •  继续学习Linux.  今天要做是的搭建Linux的内核调试环境。 环境:Ubuntu13.04 gcc4.7.3 安装QEMU QEMU简介 QEMU是一套由Fabrice Bellard所编写的以GPL许可证分发源码的模拟处理器,在GNU/Linux平台上使用...
  • ubuntu版本:ubuntu-gnome-16.04-desktop-amd64,gnome版 --------------------------------------------------------------------...syslogd是Linux下的一个记录日志文件服务。从结构来说,可以理解为这个服务下面...
  • 如何查看linux系统下的各种日志文件 linux 系统日志的分析大全 日志分类: 1. 连接时间的日志  连接时间日志一般由/var/log/wtmp和/var/run/utmp这两个文件记录,不过这  两个文件无法直接cat查看,并且...
  •  linux的启动过程,包括BIOS的加电自检POST,拷贝MBR的信息(启动BootLoader),加载内核,挂载根文件安系统这几大步熟悉grub的话会知道linux启动时grub中有三项:root,kernel,initrd。其三项的作用分别是: 1....
  • Linux内核与文件系统分析

    千次阅读 2016-08-18 15:45:29
    1、Linux内核源代码目录结构 arch:包含和硬件体系结构相关的代码,每种平台占一个相应的目录,如i386、arm、powerpc、mips等。block:块设备驱动程序I/O调度。crypto:常用加密和散列算法(如AES、SHA等),还有...
  • linux系统启动过程分析

    万次阅读 2015-11-24 00:03:47
    经过对Linux系统有了一定了解和熟悉后,想对其更深层次的东西做进一步探究。这当中就包括系统的启动流程、文件系统的组成结构、基于动态库和静态库的程序在执行时的异同、协议栈的架构和原理、驱动程序的机制等等。 ...
  • 本文介绍了Linux内核的启动过程,分析系统的关键部分代码从而可以让大家从一个大体上谅解Linux的启动过程,本人也是Linux的菜鸟级别。网络上很多的分析不太好动希望同属于初学者,这篇文章对大家有所帮助
  • static struct led_trigger timer_led_trigger = { .name = "timer", .activate = timer_trig_activate, .deactivate = timer_trig_deactivate, };

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 537,254
精华内容 214,901
关键字:

linux系统分析

linux 订阅