51工具盒子

依楼听风雨
笑看云卷云舒,淡观潮起潮落

Linux驱动基础知识

以下内容部分摘录自公众号:嵌入式与Linux那些事,仅用于个人交流与学习,如涉及侵权请联系站长删除!

1.指令 {#1-指令}

1.1 常用的Linux指令 {#1-1-常用的Linux指令}

  1. 怎么查看当前进程?怎么执行退出?怎么查看当前路径?
    查看当前进程: ps
    执行退出: exit
    查看当前路径: pwd

  2. ls 命令执行什么功能? 可以带哪些参数?
    功能
    列出指定目录中的目录,以及文件
    参数
    -a 显示所有文件及目录 (. 开头的隐藏文件也会列出)
    -l 除文件名称外,亦将文件型态、权限、拥有者、文件大小等资讯详细列出
    -r 将文件以相反次序显示(原定依英文字母次序)
    -t 将文件依建立时间之先后次序列出
    -A 同 -a ,但不列出 "." (目前目录) 及 "..." (父目录)
    -F 在列出的文件名称后加一符号;例如可执行档则加 "*", 目录则加 "/"
    -R 若目录下有文件,则以下之文件亦皆依序列出

  3. 创建目录用什么命令?

    |---------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | 1 2 3 | mkdir runoob #在工作目录下,建立一个名为 runoob 的子目录 mkdir -p runoob2/test #在工作目录下的 runoob2 目录中,建立一个名为 test 的子目录。 若 runoob2 目录原本不存在,则建立一个。(注:本例若不加 -p 参数,且原本 runoob2 目录不存在,则产生错误。) |

  4. 创建文件用什么命令?

    vi或vim

    |-----------|--------------------------------------------| | 1 | vi file1.txt #直接创建并打开一个文件file1.txt |

    touch

    |-----------|-------------------------------------------| | 1 | touch file2.txt #创建新的空文件file2.txt |

    echo

    |-------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | 1 2 | echo "this is a new file" > file3.txt #创建文件file3.txt并将this is a new file写入(说明:使用>指令覆盖文件原内容并重新输入内容,若文件不存在则创建文件。) echo "add contents" >>file3.txt #在已存在的文件补充写入新内容add contents(说明:使用>>指令向文件追加内容,原内容将保存。) |

    less 、more 、cat

    三者都是将文件内容输出到标准输出,其中less和more可以分页显示,cat是显示全部。
    三者可以根据已经存在的文件创建新的文件。假设已经存在文件1.txt。

    |---------------|-----------------------------------------------------------------| | 1 2 3 | cat 1.txt > 2.txt less 1.txt > 3.txt more 1.txt > 4.txt |

    此时创建的文件内容都和1.txt中文件内容相同。

    cd

    |-------------|------------------------------------------------------------------------------| | 1 2 | cd > file3.txt #创建新的空文件file3.txt cd >> file4.txt #创建新的空文件file4.txt |

    cd最主要的作用是切换目录,在cd后面跟>或>>再加上文件名就可以创建一个内容为空的文件。它和echo的区别之处在于echo可写文件内容,而cd并不能

  5. 复制文件用什么命令?

    |-----------|------------------------------------------------------------------------| | 1 | cp --r test/ newtest #将当前目录 test/ 下的所有文件复制到新目录 newtest 下(前->后) |

  6. 查看文件内容有哪些命令可以使用?

    |---------------------|-------------------------------------------------------------------------------------------------------------------------------------------------| | 1 2 3 4 5 6 | vi 文件名 #编辑方式查看,可修改 cat 文件名 #显示全部文件内容 more 文件名 #分页显示文件内容 less 文件名 #与 more 相似,更好的是可以往前翻页 tail 文件名 #仅查看尾部,还可以指定行数 head 文件名 #仅查看头部,还可以指定行数 |

  7. 怎么向屏幕输出带空格的字符串,比如"hello world"?

    |-----------|--------------------------| | 1 | echo hello world |

  8. 移动文件用哪个命令?改名用哪个命令?

    |-------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------| | 1 2 | mv source_file(文件) dest_file(文件) #将源文件名 source_file 改为目标文件名 dest_file mv source_file(文件) dest_directory(目录) #将文件 source_file 移动到目标目录 dest_directory 中 |

  9. 删除文件用哪个命令?如果需要连目录及目录下文件一块删除呢?删除空文件夹用什么命令?

    |-------------|---------------------------------------------------------------------------------------------------------------------------| | 1 2 | rm -rf file/directory #删除当前目录下的所有文件及目录,并且是直接删除,无需逐一确认 rm -rf directory/ #删除目录 directory,不管该目录下是否有子目录或文件 |

  10. 查找文件内容用哪个命令?

    |---------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | 1 2 3 | grep test *file #在当前目录中,查找后缀有 file 字样的文件中包含 test 字符串的文件,并打印出该字符串的行 grep -r update /etc/acpi #查找指定目录/etc/acpi 及其子目录(如果存在子目录的话)下所有文件中包含字符串"update"的文件(递归) grep -v test *test* #查找文件名中包含 test 的文件中不包含test 的行(排除) |

  11. 查找文件用哪个命令?

    |-------------|--------------------------------------------------------------------------------------------------------------| | 1 2 | find . -name "*.c" #将当前目录及其子目录下所有文件后缀为 .c 的文件列出来 find . -ctime -20 #将当前目录及其子目录下所有最近 20 天内更新过的文件列出 |

  12. cat命令

    |-----------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | 1 2 3 4 | cat -n textfile1 #把 textfile1 的文档内容加上行号后输入到屏幕 cat -n textfile1 > textfile2 #把 textfile1 的文档内容加上行号后输入 textfile2 这个文档里 cat -b textfile1 textfile2 >> textfile3 #把 textfile1 和 textfile2 的文档内容加上行号(空白行不加)之后将内容附加到 textfile3 文档里 cat /dev/null > /etc/test.txt #清空 /etc/test.txt 文档内容 |

1.2 常用的GCC指令 {#1-2-常用的GCC指令}

  1. 预处理 (-E .i)

    |-----------|-----------------------------------------------------| | 1 | gcc -E test.c -o test.i #把预处理的结果导出到test.i文件 |

  2. 编译为汇编代码(-S .s)

    |-----------|----------------------------------------------------------------------| | 1 | gcc -S test.i -o test.s #编译器将test.i翻译成汇编语言,并将结果存储在test.s文件中。 |

  3. 汇编(-c .o)

    |-----------|-------------------------------------------------------| | 1 | gcc -c test.s -o test.o #将汇编代码编译为目标文件(.o)但不链接 |

  4. 链接(无后缀)

    |-----------|----------------------------------------------------------| | 1 | gcc test.o -o test #将生成的目标文件test.o生成最终的可执行文件test |

  5. 一步到位编译(.c 无后缀)

    |-----------|------------------------------------------------------| | 1 | gcc test.c -o test #将源文件test.c编译链接为可执行文件test |

  6. 多文件编译

    |-----------|-------------------------------------| | 1 | gcc test1.c test2.c -o test |

  7. 警告处理

    |---------------|------------------------------------------------------------------------------------------------------------------------| | 1 2 3 | gcc -w test.c -o test # 忽略编译时的警告 gcc -Wall test.c -o test #编译后显示所有警告 gcc -Werror test.c -o test #在产生警告的地方停止编译 |

1.3 常用的GDB调试指令 {#1-3-常用的GDB调试指令}

  • GDB(GNU symbolic debugger)是 GNU Project 调试器

  • GNU操作系统是一种由自由软件构成的类 Unix 操作系统

    |------------------------------------------------------------------------------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 | gcc -g test.c -o test #编译时生成debug有关的程序信息 gdb test #启动调试 help #查看命令帮助,具体命令查询在gdb中输入help + 命令,简写h run #重新开始运行文件(run-text:加载文本文件,run-bin:加载二进制文件),简写r start #单步执行,运行程序,停在第一执行语句 list #查看原代码(list-n,从第n行开始查看代码。list+ 函数名:查看具体函数),简写l set #设置变量的值 next #单步调试(逐过程,函数直接执行),简写n step #单步调试(逐语句:跳入自定义函数内部执行),简写s backtrace #查看函数的调用的栈帧和层级关系,简写bt frame #切换函数的栈帧,简写f info #查看函数内部局部变量的数值,简写i finish #结束当前函数,返回到函数调用点 continue #继续运行,简写c print #打印值及地址,简写p quit #退出gdb,简写q break+num #在第num行设置断点,简写b info breakpoints #查看当前设置的所有断点 delete breakpoints num #删除第num个断点,简写d display #追踪查看具体变量值 undisplay #取消追踪观察变量 watch #被设置观察点的变量发生修改时,打印显示 i watch #显示观察点 enable breakpoints #启用断点 disable breakpoints #禁用断点 x #查看内存x/20xw 显示20个单元,16进制,4字节每单元 run argv[1] argv[2] #调试时命令行传参 set follow-fork-mode child #Makefile项目管理:选择跟踪父子进程(fork()) |

1.4 常用的驱动开发指令 {#1-4-常用的驱动开发指令}

  1. 加载/卸载驱动

    |-------------|------------------------------------------------------------------| | 1 2 | insmod/modprobe #加载驱动 rmmod #卸载驱动Linux驱动如何查看驱动模块中打印信息? |

  2. Linux驱动如何查看驱动模块中打印信息?

    |-----------|---------------| | 1 | dmesg |

  3. 如何查看内核中已有的字符设备的信息?

    lsmodmodprobelsmod可以查看模块的依赖关系,modprobe在加载模块时会加载其他依赖的模块。

  4. 如何查看正在使用的有哪些中断号?

    |-----------|-----------------------------| | 1 | cat /proc/interrupt |

2.Uboot {#2-Uboot}

2.1 什么是bootloader? {#2-1-什么是bootloader?}

  • Linux系统要启动就必须需要一个 bootloader程序,也就说芯片上电以后先运行一段bootloader程序。这段 bootloader程序会先初始化时钟,看门狗,中断,SDRAM,等外设,然后将 Linux内核从 flash(NAND, NOR FLASH,SD,MMC等)拷贝到SDRAM中,最后启动Linux内核。当然了, bootloader的实际工作要复杂的多,但是它最主要的工作就是启动 Linux内核。
  • bootloader和 Linux内核的关系就跟PC上的BIOS和 Windows的关系一样,bootloader就相当于BIOS。总得来说,Bootloader就是一小段程序,它在系统上电时开始执行,初始化硬件设各、准备好软件环境,最后调用操作系统内核

2.2 uboot启动过程中做了那些事? {#2-2-uboot启动过程中做了那些事?}

  1. 第一阶段
    初始化时钟,关闭看门狗,关中断,启动ICACHE,关闭DCACHE和TLB,关闭MMU,初始化SDRAM,初始化NAND FLASH,重定位。

    Linux的看门狗(Watchdog)是一种硬件或软件机制,用于监视系统的运行状态,以防止系统出现故障或死机的情况。它能够检测到系统的异常情况并自动采取一些预设的措施,例如重启系统或发送警报信息。

  2. 第二阶段
    初始化一个串口,检测系统内存映射,将内核映象和根文件系统映象从Flash上读到SDRAM空间中,为内核设置启动参数,调用内核。

2.3 uboot和内核如何完成参数传递? {#2-3-uboot和内核如何完成参数传递?}

  • uboot启动后已经完成了基本的硬件初始化(如:内存、串口等),接下来,它的主要任务就是加载Linux内核到开发板的内存,然后跳转到Linux内核所在的地址运行。
    PS:只要问到uboot,面试官必问uboot和内核的参数传递,所以一定要知道!
    具体是如何跳转呢?做法很简单,直接修改PC寄存器的值为Linux内核所在的地址,这样CPU就会从Linux内核所在的地址去取指令,从而执行内核代码。
  • 在前面我们已经知道,在跳转到内核以前,uboot需要做好以下三件事情:
    1. CPU寄存器的设置
      R0=0
      R1=机器类型ID;对于ARM结构的CPU,其机器类型ID可以参见 linux/arch/arm tools/ mach-types
      R2=启动参数标记列表在RAM中起始基地址
    2. CPU工作模式
      必须禁止中断(IRQs和FIQs)
      CPU必须为SVC模式
    3. Cache和MMU的设置
      MMU必须关闭
      指令 Cache可以打开也可以关闭
      数据 Cache必须关闭
      其中上面第一步CPU寄存器的设置中,就是通过R0,R1,R2三个参数给内核传递参数的。

2.4 为什么要给内核传递参数呢? {#2-4-为什么要给内核传递参数呢?}

  • 在此之前,uboot已经完成了硬件的初始化,可以说已经"适应了"这块开发板。然而,内核并不是对于所有的开发板都能完美适配的(如果适配了,可想而知这个内核有多庞大,又或者有新技术发明了,可以完美的适配各种开发板),此时,对于开发板的环境一无所知。所以,要想启动Linux内核,uboot必须要给内核传递一些必要的信息来告诉内核当前所处的环境

2.5 如何给内核传递参数? {#2-5-如何给内核传递参数?}

  • uboot把机器ID通过R1传递给内核 ,Linux内核运行的时候,首先就从R1中读取机器ID来判断是否支持当前机器。这个机器ID实际上就是开发板CPU的ID ,每个厂家生产出一款CPU的时候都会给它指定一个唯一的ID,大家可以到uboot源码的arch\arm\include\asm\mach-type.h文件中去查看。

  • R2存放的是块内存的基地址 ,这块内存中存放的是uboot给Linux内核的其他参数。这些参数有内存的起始地址、内存大小、Linux内核启动后挂载文件系统的方式 等信息。很明显,参数有多个,不同的参数有不同的内容,为了让Linux内核能精确的解析出这些参数,双方在传递参数的时候要求参数在存放的时猴需要按照双方规定的格式存放

  • 除了约定好参数存放的地址外,还要规定参数的结构 。Linux2.4.x以后的内核都期望以标记列表 (tagged_list)的形式来传递启动参数。标记,就是一种数据结构;标记列表,就是挨着存放的多个标记。标记列表以标记ATAG_CORE开始,以标记ATAG_NONE结束。

  • 标记的数据结构为tag,它由一个tag_header结构和一个联合(union)组成 。tag_header结构表示标记的类型及长度,比如是表示内存还是表示命令行参数等。对于不同类型的标记使用不同的联合(union),比如表示内存时使用tag_ mem32,表示命令行时使用 tag_cmdline。具体代码见arch\arm\include\asm\setup.h

  • 从上面可以看出,struct_tag结构体由structtag_header+联合体union构成,结构体struct tag_header用来描述每个tag的头部信息,如tag的类型,tag大小。联合体union用来描述每个传递给Linux内核的参数信息。

2.5 为什么uboot要关掉caches? {#2-5-为什么uboot要关掉caches?}

  • caches是cpu内部的一个2级缓存,它的作用是将常用的数据和指令放在cpu内部。caches是通过CP15管理的,刚上电的时候,cpu还不能管理caches。上电的时候指令cache可关闭,也可不关闭,但数据cache一定要关闭 ,否则可能导致刚开始的代码里面,去取数据的时候,从cache里面取,而这时候RAM中数据还没有caches过来,导致数据预取异常

3.文件系统 {#3-文件系统}

3.1 什么是根文件系统? {#3-1-什么是根文件系统?}

  • 根文件系统首先是一种文件系统,该文件系统不仅具有普通文件系统的存储数据文件的功能,但是相对于普通的文件系统,它的特殊之处在于,它是内核启动时所挂载(mount)的第一个文件系统 ,内核代码的映像文件保存在根文件系统中,系统引导启动程序会在根文件系统挂载之后从中把一些初始化脚本(如rcS、inittab)和服务 加载到内存中去运行,里面包含了Linux系统能够运行所必需的应用程序、库等,比如可以给用户提供操作 Linux的控制界面的shell程序、动态连接的程序运行时需要的glibc库等。

  • 文件系统和内核是完全独立的两个部分。在嵌入式中移植的内核下载到开发板上,是没有办法真正的启动Linux操作系统的,会出现无法加载文件系统的错误。

3.2 根文件系统为什么这么重要? {#3-2-根文件系统为什么这么重要?}

  • 根文件系统之所以在前面加一个"根",说明它是加载其它文件系统的"根",那么如果没有这个根,其它的文件系统也就没有办法进行加载的 。根文件系统包含系统启动时所必须的目录和关键性的文件,以及使其他文件系统得以挂载(mount)所必要的文件。例如:

    • init进程的应用程序必须运行在根文件系统上。

    • 根文件系统提供了根目录"/"。

    • linux挂载分区时所依赖的信息存放于根文件系统/etc/fstab这个文件中。

    • shell命令程序必须运行在根文件系统上,譬如lscd等命令。

  • 总结:一套linux体系,只有内核本身是不能工作的,必须要rootfs(上的etc目录下的配置文件、/bin/sbin等目录下的shell命令,还有/lib目录下的库文件等)相配合才能工作。

3.3 可执行映像文件通常由几部分构成,它们有什么特点? {#3-3-可执行映像文件通常由几部分构成,它们有什么特点?}

可执行映像文件通常由以下几部分构成:

  • 一个或多个代码段,代码段的属性为只读。
  • 零个或多个包含初始化数据的数据段,数据段的属性为可读写。
  • 零个或多个不包含初始化数据的数据段,数据段的属性为可读写。

4.中断 {#4-中断}

4.1 硬中断 / 软中断是什么?有什么区别? {#4-1-硬中断-软中断是什么?有什么区别?}

  • 硬中断

    1. 硬中断是由硬件 产生的,比如,像磁盘,网卡,键盘,时钟等。每个设备或设备集都有它自己的IRQ(中断请求)。基于IRQ,CPU可以将相应的请求分发到对应的硬件驱动上(注:硬件驱动通常是内核中的一个子程序,而不是一个独立的进程)。
    2. 处理中断的驱动是需要运行在CPU上的,因此,当中断产生的时候,CPU会中断当前正在运行的任务,来处理中断。在有多核心的系统上,一个中断通常只能中断一颗CPU(也有一种特殊的情况,就是在大型主机上是有硬件通道的,它可以在没有主CPU的支持下,可以同时处理多个中断。)。
    3. 硬中断可以直接中断CPU。它会引起内核中相关的代码被触发。对于那些需要花费一些时间去处理的进程,中断代码本身也可以被其他的硬中断中断。
    4. 对于时钟中断,内核调度代码会将当前正在运行的进程挂起,从而让其他的进程来运行。它的存在是为了让调度代码(或称为调度器)可以调度多任务
  • 软中断

    1. 软中断的处理非常像硬中断。然而,它们仅仅是由当前正在运行的进程所产生的。
    2. 通常,软中断是一些对I/O的请求 。这些请求会调用内核中可以调度I/O发生的程序。对于某些设备,I/O请求需要被立即处理,而磁盘I/O请求通常可以排队并且可以稍后处理。根据I/O模型的不同,进程或许会被挂起直到I/O完成,此时内核调度器就会选择另一个进程去运行。I/O可以在进程之间产生。并且调度过程通常和磁盘I/O的方式是相同。
    3. 软中断仅与内核相联系。而内核主要负责对需要运行的任何其他的进程进行调度。一些内核允许设备驱动的一些部分存在于用户空间,并且当需要的时候内核也会调度这个进程去运行。
    4. 软中断并不会直接中断CPU。也只有当前正在运行的代码(或进程)才会产生软中断 。这种中断是一种需要内核为正在运行的进程去做一些事情(通常为I/O)的请求。有一个特殊的软中断是Yield调用,它的作用是请求内核调度器去查看是否有一些其他的进程可以运行。

    说人话就是只会影响内核去间接调度CPU,而不会直接中断CPU去处理中断服务程序

  • 区别

    1. 软中断是执行中断指令 产生的,而硬中断是由外设引发的。
    2. 硬中断的中断号是由中断控制器提供的,软中断的中断号由指令直接指出,无需使用中断控制器。
    3. 硬中断是可屏蔽的,软中断不可屏蔽。
    4. 硬中断处理程序要确保它能快速 地完成任务,这样程序执行时才不会等待较长时间,称为上半部
    5. 软中断处理硬中断未完成的工作 ,是一种推后执行的机制,属于下半部

4.2 中断为什么要区分上半部和下半部? {#4-2-中断为什么要区分上半部和下半部?}

  • Linux中断分为硬件中断和内部中断(异常),调用过程:外部中断产生->发送中断信号到中断控制器->通知处理器产生中断的中断号,让其进一步处理。
  • 对于中断上半部和下半部的产生,为了中断处理过程中被新的中断打断,将中断处理一分为二,上半部登记新的中断,快速处理简单的任务,剩余复杂耗时的处理留给下半部处理,下半部处理过程中可以被中断,上半部处理时不可被中断。

4.3 中断下半部一般如何实现? {#4-3-中断下半部一般如何实现?}

4.4 linux中断的响应执行流程?中断的申请及何时执行(何时执行中断处理函数)? {#4-4-linux中断的响应执行流程?中断的申请及何时执行-何时执行中断处理函数-?}

  • 中断的响应流程:cpu接受中断->保存中断上下文跳转到中断处理历程->执行中断上半部->执行中断下半部->恢复中断上下文
  • 中断的申请request_irq的正确位置:应该是在第一次打开、硬件被告知中断之前。
赞(1)
未经允许不得转载:工具盒子 » Linux驱动基础知识