23中科大软院linux期末复习及试题

2020期末试题

  1. 堆栈调度相关的题,给一个程序,然后写堆栈的变化(esp,ebp),eax寄存器变化。
  2. 给出了linux内核的进程切换代码switch_to()相关的代码。 阅读源码进行判断。是否会发生连续pop和push切换。
  3. 简述linux函数调用与系统调用的异同。
  4. 以x86-64体系,简述linux系统从进程x切换到进程y的一般过程
  5. linux终端处理的流程(写出主要数据结构)
  6. inux字符设备驱动程序的主要构成(写出主要数据结构),注册以及管理运行方式。
  7. VFS文件系统的主要数据结构,进程文件的主要数据结构。
  8. 简要概述linux进程调度(写出主要数据结构)。
  9. linux计时体系的主要功能。

1. 汇编代码堆栈分析

如图所示,给了一段函数嵌套的c代码和汇编代码,汇编代码是64位的,初始化条件是rbp和rsp都指向x。
问题1:求执行c代码过程中,x-8,x-16,x-24,x-32,x-40,x-48,x-56地址中存放的数据是什么?
问题2: main函数执行完毕后,rsp和rbp指向哪里,eax寄存器中存放的数是多少。

在这里插入图片描述
掌握以下命令的替换可以很快写出。
pushl %eax //eax的值压到栈顶
等价于
subl $4,%esp //esp减4
movl %eax,(%esp)//eax的值放到esp所指向的堆栈

popl %eax//栈顶取一个数,放到eax寄存器
等价于
movl (%esp),%eax //栈顶的数值放到eax寄存器里
addl $4, %esp //栈向上回退了一个存储单元的位置

call f
等价于
pushl %eip () //执行的下一条指令地址放入堆栈
movl f, %eip (
) //f送入eip中接使用和修改。

2. 根据进程切换 switch_to() 代码,结合源码分析判断

 ((last) = __switch_to_asm((prev), (next)));
     ENTRY(__switch_to_asm)
      pushq    %rbp
     pushq    %rbx
     pushq    %r12
     pushq    %r13
     pushq    %r14
     pushq    %r15
     /* switch stack */
     movq    %rsp, TASK_threadsp(%rdi)	//保存旧进程的栈顶
     movq    TASK_threadsp(%rsi), %rsp	//恢复新进程的栈顶
 	/* restore callee-saved registers */
     popq    %r15
     popq    %r14
     popq    %r13
     popq    %r12
     popq    %rbx
     popq    %rbp

     jmp    __switch_to
 END(__switch_to

连续的push和pop操作是否发生在同一个进程的内核栈中

3. x86-64位的linux操作系统,简述函数调用和系统调用异同。

  1. 相同点
     改变指令流程
     重复执行和共用
     改变指令流后需要返回原处
  2. 不同点
     系统调用是动态调用,而函数调用时静态调用
     特权级别——系统调用工作在内核态,而函数调用工作在用户态
     进入方式不同——系统调用通过陷阱进入,函数通过call或jmp进入
     性能开销——系统调用涉及用户态到内核态的切换,相对于函数调用会有更高的开销
     访问权限——函数调用只可访问程序拥有的资源,而系统调用可以访问操作系统提供的系统资源。
     功能范围——函数调用是用户程序内部的模块调用,而系统调用提供了访问操作系统功能的接口。

4. 进程切换:简述用户态进程X切换到用户态进程Y的过程

  1. 正在运行的用户态进程X。
  2. 发生中断(包括异常、系统调用等),CPU完成load cs:rip(entry of a specific ISR)即跳转到中断处理程序入口。
  3. 中断上下文切换,具体包括如下几点:
    swapgs指令保存现场,可以理解CPU通过swapgs指令给当前CPU寄存器状态做了一个快照。
    rsp point to kernel stack,加载当前进程内核堆栈栈顶地址到RSP寄存器。
    save cs:rip/ss:rsp/rflags:将当前CPU关键上下文压入进程X的内核堆栈,快速系统调用是由系统调用入口处的汇编代码实现的。
    此时完成了中断上下文切换,即从进程X的用户态到进程X的内核态。
  4. 中断处理过程中或中断返回前调用了schedule函数,完成进程调度算法选择next进程、进程地址空间切换、switch_to关键的进程上下文切换等。
  5. switch_to调用了__switch_to_asm做关键的进程上下文切换。将当前进程X的内核堆栈切换到进程调度算法选出来的next进程(进程Y)的内核堆栈,并完成eip的状态切换,之后运行Y。
  6. 中断上下文恢复,进程Y进行中断的上下文恢复。
  7. 中断上下文恢复最后一步iret - pop cs:rip/ss:rsp/rflags,从Y的内核堆栈中弹出(3)中对应的压栈内容,完成中断上下文的切换,即从Y的内核态返回到Y的用户态。
  8. 继续运行用户态进程Y。

5. 论述linux操作系统处理中断的过程。

  1. 中断触发:硬件或软件会触发中断信号,向cpu发送中断请求。
  2. 查找中断向量表:cpu根据中断信号的编号,在中断向量表中查找对应的中断处理程序的入口。
  3. 上下文保存:保存当前的上下文到内核栈。
  4. 中断处理程序调用:cpu跳转到中断向量表中的入口地址,调用相应的中断处理程序。
  5. 中断处理程序执行:cpu执行中断处理程序,响应特定的中断事件。可能需要调用额外例程来处理中断,中断服务例程(Interrupt Service Routines,ISR)。
  6. 上下文恢复:执行完成后,恢复进程的上下文
  7. 中断返回
    数据结构
 struct irq_desc irq_desc[]{
     struct irq_chip *chip{
         // 底层的硬件访问函数
    };
     struct irqaction action{
         // 链表
         // 用户注册的中断处理函数
    }
 }

6. 论述字符设备驱动程序的组成和注册,字符设备的管理和打开访问控制。

设备驱动程序的主要构成(写出主要数据结构),注册以及管理运行方式。
组成
 头文件声明
 模块许可声明(开源许可协议 GPLv2)
 初始化、清理函数声明
 文件操作及其他内容(描述性定义)
注册
执行注册函数之前,内核向系统申请设备号。注册(在内核态下完成的)把驱动程序与对应的设备文件连接,使设备文件发出的系统调用由内核转化为驱动程序中对应的函数,同时分配一个新的device_driver描述符,对应到设备文件上。通过设备号(major number和minor number)来标识和管理字符设备,内核维护一个字符设备表,记录了每个字符设备驱动程序的信息,来管理设备的注册、分配和释放设备号等
注册时机
 如果驱动被静态编译进内核:内核初始化阶段
 作为内核模块编译: 装入模块时注册、卸载模块时销毁
注册过程
 内核中存在一个数组chrdev[]来保存所有字符设备驱动程序信息
 内核中每个字符设备都对应一个 cdev 结构的变量
 初始化(静态分配空间、动态申请结构体空间)
 字符设备注册函数:cdev_add(cdev结构指针,起始设备编号,设备编号范围)
 执行注册函数之前:向系统申请设备号
register_chrdev_region():用于已知起始设备设备号的情况 或 alloc_chrdev_region():用于设备号位置,自动避开设备号重复的冲突
 撤销函数: cdev_del(cdev结构指针)
 撤销函数执行之后: unregister_chrdev_region():释放原先申请的设备号
设备管理
内核通过设备号(major number和minor number)来标识和管理字符设备,维护一个字符设备表,记录了每个字符设备驱动程序的信息,以此来管理设备的注册、分配和释放设备号等
访问控制
驱动程序通过检查用户的访问权限、操作类型、设备状态等对设备的访问进行控制,以确保合理的权限和安全性。

7. 论述VFS文件系统的主要数据结构,以及进程相关的文件系统主要数据结构。

VFS(Virtual File System,虚拟文件系统)实现文件系统抽象,用于统一管理不同类型的文件系统。主要由一组标准的文件操作构成,以系统调用的形式提供给用户,使得不同类型的文件系统能够以一致的方式被访问和操作。
其中包括以下主要数据结构:

 /* 
 超级块        super_block
 索引节点      inode
 文件对象     file
 目录项       dentry 
 */

super_block(超级块):用于描述文件系统的整体信息(文件系统类型、挂载点、inode表的位置等)
inode(索引节点):文件或目录在文件系统中的元数据。每个文件都对应一个inode结构,包含文件的属性(如权限、大小、时间戳等)和指向数据块的指针。
dentry(目录项):建立文件路径和inode之间的映射关系。包含目录项的名称、inode指针和目录项的状态信息。
file(文件对象):表示进程打开的文件或目录。包含指向相关inode和dentry的指针,以及访问模式、位置指针等。
file_operations(文件操作集合:函数指针结构体,使得不同类型的文件系统能够以一致的方式被访问和操作。对文件的各种操作,如读取、写入、打开、关闭等。
进程相关的文件系统的主要数据结构

struct task_struct{
 struct fs_struct *fs{
         struct dentry *root;
         struct dentry *pwd;
  }; /* 文件系统信息:进程当前目录及工作目录等 */
struct files_struct *files { /* 当前打开的文件信息 */
struct file *fd_array[NR_OPEN_DEFAULT];{/* 文件对象数组,索引是文件描述符 */
      struct file_operations f_op;
        };           
    };  
 };

fs_struct:存储与进程相关的文件系统信息,包括当前工作目录、根目录等。
files_struct:进程打开文件的描述符表,维护打开文件的信息,包括文件描述符号码、文件对象指针等。
fdtable:存储进程的files_struct的实际数据。
file:表示打开的文件。文件操作集合指针指向相应文件系统的操作函数。
file_operations:使得不同类型的文件系统能够以一致的方式被访问和操作。

8. 论述:进程调度过程(主要数据结构)

 // 任务结构体,又称为进程描述符
 struct task_struct {
     // 进程标识 
     int pid;
	int state;
	stack;
     // 进程调度策略:基于动态优先权的算法
     struct policy {
         // 实时进程
         /* SCHED_RR 时间片轮转 */
         /* SCHED_FIFO 先进先出 */
 
         // 普通进程
         /* SCHED_OTHER 时间片轮转 */
    }
     int priority; // 静态优先级(不可见,只能通过接口修改)、动态优先级(由静态优先级调整而来)
     int rt_priority; // 实时进程的优先级(1-99)
     int counter; // 表示进程还可以运行多久,初始=priority; 子进程会继承父进程一半剩余时间片
 }
  1. 进程队列(双向链表):多个进程队列来管理进程,包括就绪队列、运行队列、等待队列等。其中的节点是task_struct结构。
  2. 调度函数:提供了多种调度类函数,实现不同的调度算法。调度函数负责具体的调度算法实现,包括进程选择、优先级调整、时间片分配等。
  3. 优先级:进程的优先级决定了在就绪队列中的位置和时间片分配。
  4. 时间片:使用时间片(time slice)的概念,并通过时钟中断来触发调度器进行进程切换。
    概念:
    • 活动进程:可运行状态的进程,指正在运行或等待状态的进程
    • 过期进程:已经完成执行或被终止但尚未被完全清理的进程。

9. 论述linux计时体系的功能

计时体系(Timing System)用于测量时间、延迟和时间间隔。
• 更新时间:通过系统调用(如gettimeofday()、clock_gettime()等),可以获取系统的实时时钟时间、系统启动时间等。
• 进程时间统计:通过系统调用(如clock()、clock_gettime()等),可以获取进程的用户态时间、系统态时间、执行时间等。
• 性能分析:借助计时功能,可以测量代码的执行时间,并根据结果进行优化和调整。

总结

1.系统调用

调用过程总结

系统调用从用户态陷入内核态时,从用户态堆栈转换到内核态堆栈,把esp、eip、标志寄存器等保存到内核堆栈,保存现场。系统调用入口会通过调用号执行内核处理函数,最后恢复现场和将esp、eip、标志寄存器等从内核堆栈中恢复到对应寄存器中,并回到用户态,继续执行下一条指令。

  1. syscall指令(或int $0x80)
  2. 系统调用处理入口entry_SYSCALL_64(或entry_INT80_32)
    a) 保存现场,保存中断发生时当前程序的esp、状态字、eip
    b) do_syscall_64(或do_int80_syscall_32),系统调用sys_call_table数组(内核处理函数组成),syscall_return_slowpath(regs)一直跟踪到schedule函数
    c) 恢复现场
  3. 系统调用返回iret(或sysret)
  4. 继续执行下一条指令

系统调用的意义

为用户态进程与硬件交互提供了接口。系统调用具有以下功能和特性。
• 把用户从底层的硬件编程中解放出来。
• 极大地提高系统的安全性。
• 使用户程序具有可移植性。

2.进程创建

内核进程初始化

init_task为第一个进程(0号进程)的进程描述符结构体变量,初始化是通过硬编码方式固定下来的。其他所有进程的初始化都是通过do_fork复制父进程的方式初始化的。do_fork调用copy_process()复制父进程、获得pid、调用wake_up_new_task将子进程加入就绪队列等待调度执行等

struct task_struct init_task
= {
#ifdef CONFIG_THREAD_INFO_IN_TASK
    .thread_info    = INIT_THREAD_INFO(init_task),
    .stack_refcount    = REFCOUNT_INIT(1),
#endif
    .state        = 0,
    .stack        = init_stack,
    .usage        = REFCOUNT_INIT(2),
    .flags        = PF_KTHREAD,
    .prio        = MAX_PRIO - 20,

0号进程初始化最后的rest_ init通过kernel_thread创建了1号和2号两个内核线程,实际上是复制0号进程,修改了进程pid等,1号是kernel_init,是所有用户进程的祖先,在0号进程的基础上,修改一些信息并加载一个init可执行程序。另一个是kthreadd,是所有内核线程的祖先,负责管理所有内核线程。

noinline void __ref rest_init(void)
{
…
 pid = kernel_thread(kernel_init, NULL, CLONE_FS);
…
 pid = kernel_thread(kthreadd, NULL, CLONE_FS | CLONE_FILES);}

_do_fork创建进程把当前进程的描述符等相关进程资源复制一份,从而产生一个子进程,并根据子进程的需要对复制的进程描述符做一些修改,然后把创建好的子进程放入运行队列。

用户态创建进程的方法

fork是用户态创建子进程的系统调用接口。把当前进程复制了一份,两个进程执行相同的代码,只是父进程和子进程中的返回值不同。
fork创建了一个子进程,子进程复制父进程所有的进程信息,包括内核堆栈、进程描述符等,wake_up_new_task()将子进程添加到就绪队列,使之有机会被调度执行,进程的创建工作就完成了,子进程就可以等待调度执行,子进程从ret_from_fork开始执行。当子进程获得CPU开始运行时,从用户态空间来看,就是fork系统调用的下一条指令。但fork系统调用在子进程当中也是返回的,父进程正常fork系统调用返回到用户态,fork出来的子进程也要从内核里返回到用户态。

execve与fork的区别与联系

内核装载可执行程序的过程,实际上是执行一个系统调用execve。
fork在陷入内核态之后有两次返回,第一次返回到原来的父进程的位置继续向下执行,所以它稍微特殊一点。在子进程中fork也返回了一次,会返回到一个特定的点——ret_from_fork,通过内核构造的堆栈环境,它可以正常系统调用返回到用户态
可执行程序执行到execve时陷入内核态,在内核里面用do_execve加载可执行文件,把当前进程的可执行程序给覆盖掉。系统调用返回时,返回的是新的可执行程序。

进程调度时机

进程调度时机就是内核调用schedule函数的时机
• 用户进程主动调用系统调用进入中断上下文,系统调用返回用户态之前进行进程调度。
• 内核线程或可中断的中断处理程序,执行过程中发生中断,在中断返回前进行进程调度。
• 内核线程主动调用schedule函数进行进程调度。
中断处理程序执行过程和主动调用schedule函数进行进程调度。

进程切换

进程执行环境的切换:一是从就绪队列中选择一个进程(pick_next_task),由进程调度算法决定一个进程作为下一个进程(next);二是完成进程上下文切换context_switch,进程上下文包含了进程执行需要的所有信息,即用户地址空间(代码、数据、用户堆栈等),控制信息(进程描述符、内核堆栈等),CPU寄存器的值

3.ELF文件

类型
可重定位文件:中间文件,保存着代码和数据,由编译器和汇编器创建,一个源代码会生成一个可重定位文件。可以和其他文件一起来创建一个可执行文件或者动态链接库文件。
可执行文件:由多个可重定位文件结合生成,完成所有重定位工作和符号解析的文件(动态链接库符号是在运行时解析的),文件中保存着一个用来执行的程序。
动态链接库文件(共享目标文件):经过链接处理可以直接加载运行的库文件,是可以被可执行文件或其他动态链接库文件加载使用的库文件。

2021.5 期末考题

1. 汇编代码堆栈分析 rbp(x),rip(24):

2. 根据64位switch_to_asm分析:详解切换过程,关键rip如何切换

switch_to_asm将当前任务切换到新的任务:
(1) 保存旧任务的寄存器状态:包括通用寄存器(如rax、rbx等)、控制寄存器(如cr2、cr3等)以及其他特殊寄存器。
(2) 加载新任务的页表:
(3) 切换堆栈:将新任务的堆栈指针(存储在任务的rsp)加载到rsp寄存器中,切换到新任务的堆栈。
(4) 切换rip:通过将新任务的rip中的值加载到rip寄存器以继续执行新任务的代码。
关于rip切换的详细过程如下:

3. fork进程堆栈:画出堆栈相对布局、用户态/内核态进程指令开始地址

在这里插入图片描述

在这里插入图片描述

struct thread_struct数据结构最关键的是sp和ip。sp用来保存进程上下文中的ESP寄存器状态,ip用来保存进程上下文中的EIP寄存器状态;

4. 命令行输入./hello,尽可能详细的描述发生了哪些事件,关键步骤有哪些

在这里插入图片描述

程序从源代码到可执行文件步骤大致分为:预处理、编译、汇编、链接。
• 汇编后形成的.o格式的文件是ELF格式文件了。生成的目标文件至少.text、.data和.bss。
• 链接将.o文件或库文件组合成为一个单一文件的过程,可被加载(或被复制)到内存中并执行。链接分为静态链接和动态链接,静态链接在链接时直接将需要的执行代码复制到最终可执行文件中。 动态链接在程序运行或加载时将将需要的动态库加载到内存中。
execve系统调用接口函数将命令行参数和环境变量传递给可执行程序的main函数。

5. linux操作系统处理中断的过程及主要数据结构

6. 字符设备驱动程序的组成,是如何跟设备文件相关联的?

7. 进程调度相关:活动进程、过期进程;RR调度后,普通进程和实时进程的变化

8. VFS主要作用和主要数据结构

2023.5期末试题

  1. 与往年一致,看代码画堆栈
  2. 往年代码一致(第二题),rip.rsp,pre-thread.sp执行完后分别指向哪儿,prev的内核堆栈存放哪些
  3. 进程地址空间切换过程
  4. fork执行过程
  5. linux一般执行过程

Linux系统的一般执行过程

1、Linux系统的启动过程通常称为引导过程(Boot process),包括一系列步骤,具体如下:

(1)BIOS(Basic Input/Output System):计算机上电后,首先执行的是BIOS。BIOS会进行POST(Power-On Self Test,开机自检),检查硬件是否正常。然后搜索、加载并执行引导加载程序。

(2)Bootloader(引导加载程序):Bootloader的作用是加载内核。在许多Linux系统中,GRUB(GRand Unified Bootloader)是最常用的Bootloader。GRUB被加载到内存中,并开始执行,加载Linux内核。

(3)Kernel(内核):内核是Linux系统的核心,负责管理系统的硬件资源,并提供上层应用程序运行所需的环境。内核首先进行自我解压,然后进行一系列硬件检测与初始化。之后,挂载根文件系统并运行第一个进程。

(4)Init进程:Init进程是Linux系统中的第一个进程(PID为1),负责启动其他所有进程。不同的系统可能使用不同的Init系统,如System V、Upstart、systemd等。systemd是目前许多现代Linux发行版的默认选择。

(5)Runlevel(运行级别):Init进程启动后,根据系统的运行级别启动其他服务。Linux系统有7个运行级别(0-6),每个级别对应一组特定的服务。例如,运行级别3通常是多用户文本模式,运行级别5通常是图形界面模式。

(6)用户空间初始化:在这个阶段,Init进程启动一系列后台服务和守护进程,包括网络服务、日志服务、调度服务等。如果系统配置为图形界面登录,还会启动界面系统,并在最后显示登录界面。

解压引导程序的任务是将压缩的内核代码解压缩到内存中合适的位置,然后跳转到解压后的内核入口点开始执行内核代码。这个过程被称为"内核自我解压"。完成自我解压后,内核开始初始化操作,包括设备检测、驱动加载、内存管理单元设置等,并最终挂载根文件系统,运行Init进程。
6. 中断描述符表的内容和作用
7. 发生中断时,内核堆栈的变化
8. 软定时器的处理过程
9. vfs的作用,文件描述符,文件结构,文件操作函数的关系

感谢往届学长提供的参考
2020中科大软件学院linux操作系统分析期末考试题
中科大软件学院《linux操作系统分析》期末考试.md

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Flyy.

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值