Linux知识点总结

Linux知识点总结包括进程、线程、锁、GDB、可执行程序和测试等内容。其中包括进程的创建、中止和等待,线程的创建、退出、调度和CPU绑定,不同类型的锁(互斥锁、读写锁、自旋锁和条件变量),以及使用GDB进行调试和可执行程序的编译、符号表和动态链接等。

type
status
date
slug
summary
tags
category
icon
password

目录

前言

有道无术,术尚渴求。有术无道,止于术。
软件质量要求标准:功能->可靠性,易用性,高效性,可维护性,可移植性
如何持续提高软件质量方法:P(计划)D(执行)C(检查)A(处理)

1. Linux异构系统

1.1 Xenomai

是实现操作系统,双核结构,在原有linux内核中添加了一个具有实时性的内核,新增实时性的libcobalt库,兼容glibc库,且两个库可以相互通信

1.2 openamp

是非对称多处理框架,可以根据具体情况配置每个CPU核的环境和应用。

1.3 Jaihouse

嵌入式系统的虚拟化工具,非常强调partition域,partition的隔离性非常好,实时性较好,由德国西门子开发,2013年11发布。

1.4 Hypervisor

虚拟化技术,通过将物理资源视为可以动态分配虚拟源的池,提高资源(CPU,存储,网络)利用率。有两种工作方式:
  1. Bare-metal虚拟化:直接运行在硬件上
  1. Host OS虚拟化:运行在另外一个操作系统中

2. Linux驱动

2.1 字符设备驱动

只能一个字节一个字节的读写的设备,不能随机读取设备内存中的某一数据,读取数据需要按照先后顺序进行。字符设备是面向流的设备,常见的字符设备如鼠标、键盘、串口、控制台、LED等。
应用程序操作的常用接口有:open, close, read, write, ioctl五个接口。对应驱动程序的接口为file_operations,该接口用来直接操作硬件。file_operations中的操作接口全部是用回调函数来实现具体操作的。
注册框架:
  • 分配设备号:register_chardev_region()alloc_chardev_region()
  • 初始化并注册cdev: cdev_init() cdev_add()
  • 编写删除设备的函数:cdev_del() unregisterc_chardev_region()
  • 编写具体的操作函数:file_operations()
  • 创建设备节点: 自动创建 class_create() device_create();手动创建 mknode /dev/设备名称 主设备号 从设备号
notion image
字符驱动缺点:设备信息和驱动代码混合在一起
优化后的模型:总线驱动来统筹,用设备树来配置设备信息余擦配置方法解耦,代码里面of_XXX的代码都是用设备树来管理的。设备树存放路径 /sys/firmware/fdt 路径中的DTB文件。

2.2 网络驱动:

网络设备比较特殊,不在是对文件进行操作,而是由专门的网络接口来实现。应用程序不能直接访问网络设备驱动程序。在/dev目录下也没有文件来表示网络设备。

2.3 块设备驱动:

是指可以从设备的任意位置读取一定长度的数据设备。块设备如硬盘、磁盘、U盘和SD卡等存储设备。

3. Linux内存

3.1 应用层内存申请方式

堆上申请:malloc(free) new(delete) alloc(free) mmap(munmap) 全局变量
栈上面申请: 变量直接申请 char buf[1024]={0};

3.2 内核内存申请接口

kmalloc(kfree) 申请的内存位于物理内存映射区域,而且在物理上也是连续的,它们与真实的物理地址只有一个固定的偏移,因为存在较简单的转换关系,所以对申请的内存大小有限制,不能超过128KB
kzalloc(kfree) kmalloc() 非常相似除了申请内核内存外,还会对申请到的内存内容清零
vmalloc(vfree) 在虚拟内存空间给出一块连续的内存区,但这片连续的虚拟内存在物理内存中并不一定连续。 vmalloc() 没有保证申请到的是连续的物理内存,因此对申请的内存大小没有限制,如果需要申请较大的内存空间就需要用此函数了。

3.3 防止内存溢出

应用程序编译添加参数 gcc –fstack-protectpr –fstack-protector-all
字节对齐

3.4 字节大小端问题

大端:高位字节存放在低地址
小端:高位字节存放在高地址
ARM 默认小端

4. 设计模式

4.1 有限状态机(FSM)模式

一种用来进行对象行为建模的工具,用于秒速对象在它的生命周期内所经历的状态序列,以及如何相应来自外界的各种事件
使用场景:建模应用行为,硬件电路系统设计,软件工程,编译器,网络协议等。
典型应用:TCP协议状态机,自动售货机
状态机组成:
  • 状态:所有可能的状态,包括当前状态和满足条件后要转移的状态
  • 事件:状态转移的条件,一个条件满足,会触发一个动作,并执行状态转移
  • 动作:条件满足后执行的动作(可选),动作执行完后,可转移到新状态,也可以保持原状态。
有限状态机类型:
  • Moore 类型:输出只与当前状态有关(与输入事件无关),下一个状态取决于大当前的状态
  • Mealy 类型:输出和当前状态及输入事件有关。下一个状态取决于“当前态”和“事件”。
有限穷举法语法结构:在实现上用 switch caseif else
穷举法缺点:实现状态之间转换时,检查转换条件和进行状态转换都是混杂在当前状态中完成的
优化方法:抽象接口,把相应方法进行提前注册,然后查表,这样代码结构简单,逻辑清晰。

4.2 命令模式

是一种数据驱动的设计模式,它属于行为型模式。请求以命令的形式包裹在对象中,并传给调用对象。调用对象寻找可以处理该命令的合适的对象,并把该命令传给相应的对象,该对象执行命令。
使用场景:在软件系统中,行为请求者与行为实现者通常是一种紧耦合的关系,但某些场合,比如需要对行为进行记录、撤销或重做、事务等处理时,这种无法抵御变化的紧耦合的设计就不太合适。
关键代码:定义三个角色:1、received 真正的命令执行对象 2、Command 3、invoker 使用命令对象的入口
命令模式结构示意图:
notion image

5. IPC

进程:基本资源(地址空间,数据,堆,文件)的分配单元,也是执行单元
线程:CPU调度单元,包含栈,寄存器
操作系统的两种状态:用户态,内核态
用户态:主要运行用户程序,需要通过系统调用,异常,外围中断的方式来访问内核态
内核态:运行操作系统的程序,硬件操作

5.1 进程

进程资源分配的最小单元,在linux中进程ID是用来区分进程的,因为进程ID(PID)是在系统中的唯一标识。可以用命令ps –aux 来查询
notion image
LUNX下应用层进程函数有:
  1. 创建子进程
    1. 函数:
      子进程会复制父进程的PCB,二者之间代码共享,数据独有,拥有各自的进程虚拟地址空间。
      如父进程中有全局变量g_val初值为10,子进程创建之后通过复制父进程的PCB,并且二者的进程虚拟地址空间通过页表映射到同一块物理内存,但如果子进程更改了g_val的值,就会在物理内存中开辟新的空间并保存属于子进程的g_val:
      notion image
      函数:
      它和父进程共享同一个进程虚拟地址空间,在其创建出子进程之后,让子进程先执行,而父进程则会阻塞,直到子进程执行完毕,父进程才会开始执行,这样就避免了调用栈混乱的问题。
      notion image
  1. 中止进程
    1. exit函数:
      1. status定义了进程的终止状态,由用户自己传递,父进程可以通过wait来获取该值。刷新缓存后进程在退出
    2. _exit函数
      1. 进程直接退出
        notion image
        exit()_exit()还有一个很重要的区别就是在退出前会不会刷新缓冲区。显然,前者是会刷新缓冲区的
  1. 进程等待:
    1. 进程等待的作用就是防止僵尸进程的产生。父进程通过进程等待的方式,回收子进程的资源,获取子进程的退出状态。
    2. wait函数
      1. 返回值:成功会返回被等待进程的pid,失败则会返回-1
        参数:一级指针status,它其实是个输出型参数,用于获取子进程的退出状态,如果不关心则可以设置为NULL
    3. waitpid函数
      1. 返回值:等待成功正常返回则返回被等待进程的pid如果第三个参数options设置成了WNOHANG,而此时没有子进程退出(没有成功等待到子进程),就会返回0,而不是阻塞在函数内部调用出错则返回-1
        参数:pid,设置成-1则表示等待任意一个子进程,同wait;如果>0则表示等待一个指定的子进程,pid就是被等待子进程的进程号status,出参,获取子进程的退出状态,同waitoptions,可以设置为0或WNOHANG。设置为0则与wait一样,如果没有等待到子进程退出会一直阻塞;而设置为WNOHANG则表示非阻塞,如果被等待的子进程未退出,则会返回0值,成功等待到子进程则会返回被等待子进程的pid
    4. waitpidwait区别
      1. wait会阻塞等待子进程,waitpid则不会。
  1. 进程间通信
    1. 套接字通信
      1. 用套接字函数来创建网络链接。系统内进程间通信,也可以跨系统进程间通信
    2. 信号量
      1. 保护进程间临界资源
        1) 创建/获取一个信号量集合
        2) 信号量结合的操作
    3. 共享内存
      1. notion image
        1) 创建共享内存
        2) 挂接函数
        返回值:返回这块内存的虚拟地址;
        shmat的作用是将申请的共享内存挂接在该进程的页表上,是将虚拟内存和物理内存相对应;
        3) 去挂接函数
        返回值:失败返回-1;shmdt的作用是去挂接,将这块共享内存从页表上剥离下来,去除两者的映射关系;
        shmaddr:表示这块物理内存的虚拟地址。
        4) shmctl 函数
        shmctl用来设置共享内存的属性。当cmdIPC_RMID时可以用来删除一块共享内存。
        5) 共享内存类似消息队列和信号量,它的生命周期也是随内核的,除非用命令才可以删除该共享内存;
    4. 管道
      1. 无名管道创建函数:
        命名管道创建函数
    5. 消息队列
      1. 消息队列,就是一个消息的链表,是一系列保存在内核中消息的列表。用户进程可以向消息队列添加消息,也可以向消息队列读取消息。消息队列与管道通信相比,其优势是对每个消息指定特定的消息类型,接收的时候不需要按照队列次序,而是可以根据自定义条件接收特定类型的消息。可以把消息看做一个记录,具有特定的格式以及特定的优先级。对消息队列有写权限的进程可以向消息队列中按照一定的规则添加新消息,对消息队列有读权限的进程可以从消息队列中读取消息。
        notion image

5.2 线程

线程Linux下最小可执行单元
notion image
进程内部的线程间共享资源有:
  1. 文件描述符表
  1. 每种信号的处理方式
  1. 当前工作目录
  1. 用户ID和组ID
  1. 内存地址空间 (.text/.data/.bss/heap/共享库)
进程内部的线程间非共享资源有:
  1. 线程id
  1. 处理器现场和栈指针(内核栈)
  1. 独立的栈空间(用户空间栈)(自己的栈空间)
  1. errno变量
  1. 信号屏蔽字
  1. 调度优先级
线程控制函数:
  1. 获取线程ID
    1. 函数原型:
  1. 创建一个新线程
    1. 函数原型:
      参数:
    2. thread:传出参数,保存系统为我们分配好的线程ID
    3. attr:通常传NULL,表示使用线程默认属性。若想使用具体属性也可以修改该参数,后面线程属性会详细讲解。
    4. start_routine:函数指针,指向线程主函数(线程体),该函数运行结束,则线程结束。
    5. arg:传递给线程主函数执行期间所使用的参数,回调函数。
  1. 线程退出
    1. 函数原型:
      主要是子线程主动退出,主线程调用会整个进程退出
  1. 阻塞等待线程退出
    1. 函数原型:
  1. 实现线程分离
    1. 线程分离状态:指定该状态,线程主动与主控线程断开关系。线程结束后,其退出状态不由其他线程获取,而直接自己自动释放。
      网络、多线程服务器常用。
      函数原型:
  1. 杀死(取消)线程
    1. 函数原型:
      常用于主线程对子线程的控制,子函数退出时,具有延时性。需要运行到一个系统调用时才能退出,比如open,close,write,read,ioctol。也可以人为设置,调用函数:
  1. 线程策略函数
    1. 线程调度策略设置函数
      函数原型:
      线程调度策略查询函数
      函数原型:
      获取系统调度策略最大值
      函数原型:
      获取系统调度策略最小值
      函数原型:
  1. 线程优先级函数
    1. 线程优先级设置函数
      函数原型:
      线程优先级查询函数
      函数原型:
  1. 线程CPU绑定
    1. 从函数名以及参数名都很明了,唯一需要点解释下的可能就是cpu_set_t这个结构体了。这个结构体的理解类似于select中的fd_set,可以理解为cpu集,也是通过约定好的宏来进行清除、设置以及判断:

6. 锁

用于线程资源安全

6.1 互斥锁

只要有一方获取了锁,另一方则不能继续获取,进而执行临界区代码。
函数原型:
初始化
销毁
加锁
解锁
尝试加锁

6.2 读写锁

适合于对数据结构的读次数比写次数多得多的情况。因为,读模式锁定时可以共享,以写模式锁住时意味着独占,所以读写锁又叫共享-独占锁
函数原型:
初始化
销毁
读加锁
写加锁
解锁
尝试读加锁
尝试写加锁

6.3 自旋锁

轮询忙等待。在单核cpu下不起作用:被自旋锁保护的临界区代码执行时不能进行挂起状态。会造成死锁。
自旋锁的初衷就是:在短期间内进行轻量级的锁定。一个被争用的自旋锁使得请求它的线程在等待锁重新可用的期间进行自旋(特别浪费处理器时间),所以自旋锁不应该被持有时间过长。如果需要长时间锁定的话,最好使用信号量。
notion image

6.4 条件变量

互斥锁不同,条件变量是用来等待而不是用来上锁的。条件变量用来自动阻塞一个线程,直到某特殊情况发生为止。通常条件变量和互斥锁同时使用。
函数原型

7. GDB

7.1 GDB调试方法

gdb [可执行程序]
notion image
这个不待参数的,带参数的 gdb –args [程序名] [参数1 参数2 …]
也可以支持gdb远程调试,需要用到gdbserver进行命令远程收。
A、在目标机上面运行 gdbserver :[端口] [要调试程序名称]
notion image
B、在本地运行gdb
然后输入 target remote [gdbserver所在的ip]:[gdbserver制定的端口]
notion image

7.2 其他调试手段

  1. 打断点:
    1. b 函数名称 或者 b 源文件名:行号
  1. 查看断点:
    1. i b
      notion image
      r 启动|运行
      n [x] 下x步,不进入子函数
      c 继续运行
      notion image
  1. 删除断点:
    1. d [断点号] 断点号用i b 查看
      d breakpoints 删除所有断点
      notion image
  1. 反汇编
    1. disassemble 函数名|地址
      notion image
      notion image
      disassemble /rm 函数名|地址 源代码+汇编语言形式
      notion image
      layout split 源码+汇编调试
      notion image
      layout src 源码调试
      notion image
      layout regs 源码+寄存器调试
      notion image
      layout asm 汇编语言调试
      notion image
  1. 显示变量
    1. p/x 变量名称
      ptype 变量名称 显示变量类型
      notion image
  1. 修改变量
    1. set 变量名=值
  1. 显示寄存器值
    1. i registers
      notion image
  1. 显示线程信息
    1. info threads
      notion image
      tread [id] 切换到制定线程
      thread apply [id] continue 指定id线程继续运行
      thread apply all tontinue 进程中所有线程继续运行
      set scheduler-locking on 仅当前被调试线程继续运行

8. 可执行程序

  1. 可执行程序编译过程
    1. 可执行程序文件格式:Elf格式
      用命令readelf查看
      readelf –h [程序名称] 获取elf头
      notion image
      notion image
  1. 可执行程序符号表
    1. nm 命令:nm [程序名称] |grep [函数名] 获取该程序函数地址
      readelf –s 导出详细符号表
      notion image
      可以得出某些函数是否有异常,有没有实现之类的
  1. 动态加载动态库
    1. 程序运行时动态加载动态库,可以根据配置加载不同的库
      dlopen() 加载动态库
      dlerror() 获取加载异常
      dlsym() 获取动态库函数地址
      dlcolse() 关闭动态库
  1. 动态链接和静态链接
    1. 静态链接缺点:相同代码时,浪费内存和存储空间。模块更新必须重新编译程序
      动态库链接:与地址无关,对环境强耦合,当库丢失掉,程序不能运行

9. 测试

按测试技术分类:
  • 黑盒测试:主要是功能测试,测试各个功能是否正常,常用的测试框架有jenkins
  • 白盒测试:主要是开发人员使用,测试代码功能,测试关键接口函数。常用的测试框架CTEST
  • 灰盒测试:跨与测试,双方约定接口,当对方没有提供库或者功能未实现。就用打桩技术,模拟实现接口,测试自己的逻辑。
Loading...