• 《嵌入式Linux C编程入门》阅读笔记&书评


    作者: 华清远见嵌入式培训中心 出版社: 人民邮电出版社
    全称:嵌入式Linux C编程入门(第二版)
    CUIT索书码:TP316.81/H759-1/E2(盘)

    CH1 嵌入式系统的基础知识

    • 冯诺依曼结构:具有公用的数据存储空间和程序存储空间,他们共享存储总线,这也是以往设计时常用的方式。
    • 哈佛结构具有分离的数据和程序空间以及分离的访问总线。哈佛结构在指令执行时,取值和取数可以并行,因此具有更高的执行效率。
    
    • 1
    • 2

    CH2 嵌入式Linux C语言开发工具

    vi是Linux系统的第一个全屏幕交互式编辑程序,按不同的使用方式可以分为3种状态:命令行模式、插入模式、底行模式。(编辑)
    GCC编译器家族:支持c/c++,java等语言。(编译)
    GDB:GNU开源组织发布的一个强大的Linux下的程序调试工具。(调试)
    Makefile:自动编译管理器,能够根据文件时间戳自动发现更新过的文件,减少编译的工作量。(管理)
    Emacs:集编辑、编译、调试于一体的开发环境。(综合)

    CH3 构造嵌入式Linux系统

    CH4 嵌入式Linux C语言基础——数据、表达式

    数据类型
    分为三类:
    • 基本类型
    ○ 整形
    ○ 浮点型
    ○ 字符型
    ○ 枚举型
    ○ 指针型
    • 空类型
    • 构造类型
    ○ 数组
    ○ 结构体
    ○ 共用体
    字符串与字符常量
    “a”“D”为字符串,‘a’为字符常量。
    与字符常量相对应的是字符变量:char,占用一个字节的空间。
    Const
    在关键字后面加上“const”即可表示定义的是常量:
    int const a=10;则a的值只能是10,不能修改。
    const涉及指针的用法:
    Int const *a; 表示const的是整形数组,所以a指向的数组(当然也有可能是数据)是不能改变的。
    Int *const a; 表示const的是a,表示指向a的地址是不能变的,但是但是a指向的数据可以改变。
    地址/偏移
    32为的线性地址:
    页全局目录入口 页中间目录入口 页表项 偏移
    Linux内核建立页面主要通过三个while循环创建页表地址映射规律。

    CH5 嵌入式Linux C语言基础——控制语句及函数

    3中程序结构
    顺序结构、分支结构、循环结构。
    输入输出函数
    • 字符串输入输出:gets,puts
    • 字符输入输出:getchar,putchar
    • 格式输入输出:scanf,printf
    字符输入输出格式:
    输入:getchar();
    输出:putchar(字符);
    格式输入输出格式:
    输入:scanf(格式说明,&变量);如:printf(“a=%d”,a);
    输出:printf(格式说明,输出变量);如:scanf(“a=%d”,&a);
    其中,格式说明由%和格式字符组成,具体形式如下表:
    十进制整数 d
    八进制整数 o
    十六进制整数 x
    unsigned型数据 u
    一个字符 c
    一个字符串 s
    实数(单双精度) f
    指数形式 e
    条件函数
    switch:
    switch函数的选择由case和default构成,一般习惯把default放在最后,但放在前面也不影响。选择语句的后面通常都有break,否则会无视下面的case和default,一直运行到switch的最后一行语句。在确实不需要加break的地方,建议使用/no break/进行标识,便于检查。
    break和continue:
    Break Continue
    用在循环体或switch中,表示跳出当前循环体或当前的switch。 用在循环体中,表示跳过当前循环的其余代码,不跳出循环,而是进入下一组。
    【注】不能跳出if
    形参与实参
    函数调用时,调用语句后面括号里面的参数是实参,函数执行时,函数体中的参数是形参。在函数调用时,实参的数据赋值给形参,这个赋值是单向的,即:只能实参→形参。不论函数运行时形参的值怎样改变,都不能改变实参的值,若想将计算的值传递出来,只能使用return或指针(指针作为函数参数)。

    递归:递归函数可以使代码变短,但因为执行时需要保存每一步的参数、变量等,所以会产生很多内部开销,“非必要不使用”。

    CH6 嵌入式Linux C语言基础——数组、指针与结构

    在创建数组时赋值:
    Char a[3]={‘a’,‘b’};
    则a[2]被赋值为\0,若不是char类型的数组,则a[2]被赋值为0.
    上述赋值也可用:char a[3]={“ab”};
    二维数组的赋值:
    Int a[2][2]={{1,2},{3,4}}; 或:
    Int a[2][3]={1,2,3,4}
    给P1分配动态内存:
    Int p1;
    If((p1=(int
    )malloc(sizeof(int)))==NULL){
    Perror(malloc);
    Return;
    }
    释放:
    Free(p1);
    动态内存分配问题的函数原型:void malloc(size_t size);
    指针作为函数参数
    将指针作为实参,传递给函数,函数内部虽然不能改变指针的值,但可以通过指针找到指向的数据,从而对它进行更改,显示为对
    p更改。
    还是比较好理解的。

    &a与&a[0]等价。
    函数指针
    形式:int (*p)();
    函数名即为函数入口,所以对于p,可以换成别的函数名。

    CH7 嵌入式Linux C语言基础——高级议题

    预处理、文件包含、堆与栈。
    起到函数作用的宏定义
    eg:#define MAX(a,b) ((a>b)?a:b)
    使用时直接使用MAX(x,y)即可表示(x>y)?x:y
    条件编译
    格式:
    #ifdef a
    {
    printf(“1”);
    }
    #else
    {
    printf(“2”);
    }
    #endif
    作用为:如果前面define了a(即使是#define a这样也算定义过),则输出1,否则输出2.
    【注】后面要加上#endif,像VB一样。
    条件编译不满足的部分是不会编译的,能缩短目标程序。
    堆与栈
    堆: 一般由程序员分配(malloc)和释放(free), 若程序员不释放,程序结束时可能由OS(操作系统)回收,分配方式倒是类似于链表。
    栈:由操作系统自动分配释放 ,存放函数的参数值,局部变量的值等。其操作方式类似于数据结构中的栈。
    【注】静态变量不入栈。
    memory描述符

    #CH8 嵌入式Linux C语言基础——ARM Linux内核常见数据结构
    链表
    四种链表的指标:
    单向链表 双向链表 单向循环链表 双向循环链表
    指针域 Next next,priv Next next,priv
    结尾指针 NULL NULL 头指针 头指针
    内存占用 较少 较多 较少 较多
    操作灵活性 较不灵活 较为灵活 较为灵活 非常灵活
    时间复杂度与空间复杂度 O(N) O(N) O(N) O(N)
    在Linux中,关于链表的接口:
    • 初始化:static inline int list_empty(const struct list_head *head))
    • 插入:static inline void _list_add(stuct list_head *new, struct list_head *head)
    • 删除:static inline void _list_del(struct list_head *prev,struct list_head *next)

    树、二叉树、平衡树
    二叉树:
    • 特殊形态的二叉树:
    ○ 满二叉树:满的
    ○ 完全二叉树:除了最下面一层仅有从右向左缺失的若干节点,其余层都是满的。
    • 顺序存储:空间利用率高、寻找孩子和双亲比较容易;但插入和删除节点不容易(需要整体移动数组)
    ○ 第一层,再第二层,再第三层。。。每层从左往右。
    • 链式存储:顺序存储依靠编号寻找亲子关系,所以非完全二叉树需要填补空缺,效率低。
    节点结构:
    Lchild Item Rchild
    ○ 所有右孩子的数值大于根节点,所有左孩子的数值小于根节点
    • 二叉树的遍历(名字里面先、中、后表示更节点的访问次序)
    ○ 先序遍历:先根,再左,再右
    ○ 中序遍历:先左,在根,再右
    ○ 后序遍历:先左,再右,再根
    平衡树:因为二叉树各个子树之间的高度相差可能很大,容易造成平均性能下降,所以提出了“平衡树”。通常包括B树、AVL书、红黑树等,能保证最坏情况下为O(logN)的性能。
    哈希表
    哈希表构造方法:直接定址法、数字分析法、折叠法、除留余数法、随机数法。
    冲突处理方法:开放定址法、再哈希法、链地址法、建立一个公共溢出区。

    CH9 文件I/O相关实例

    通用文件模型
    • 超级块:super block,存放系统中已安装文件系统的有关信息。每个文件系统对应一个超级块对象。
    • 索引节点:inode,每个文件对应一个索引节点。
    • 目录项:dentry,存放目录项与对应文件链接的信息。
    • 文件:file,存放打开文件与进程之间进行交互的有关信息。
    拓扑:进程→文件→目录项→索引节点→(超级块→)磁盘文件
    文件I/O
    • Close
    • Open
    • Read
    • Write
    • lseek:用于将文件指针定位到相应的位置。
    • Fcntl:功能较多(获得/设置文件标记、获得/设置记录锁等)
    • Select:选择I/O复用的功能
    串口

    CH10 ARM Linux进程线程开发实例

    进程
    进程类型:
    • 交互进程
    • 批处理进程
    • 守护进程
    进程状态:
    • 运行(running)
    • 可中断(interruptible)
    • 不可中断(uninterruptible)
    • 僵死(zombie):进程运行结束,等待父进程销毁。
    • 停止(stopped)
    进程优先级在0139中间,其中实时进程(软实时)占用099,一般进程占用100~139。
    线程
    从内核角度看,Linux不存在线程的概念,所有线程都当做进程来实现,他们具有属于自己的task_stuct,只是这些线程会共享一些资源而已。

    进程控制的一些API:
    • fork:Linux中创建新进程的位移方法。从已存在的进程中创建一个新进程。
    • exec(函数族,共有6个以exec开头的函数,具体的功能有细微差别):创建一个子进程,子进程几乎复制了父进程的全部内容。
    使用情况:
    ○ 当进程认为自己没用时,调用任意一个exec函数,让这个进程重生。
    ○ 如果进程想执行另一个程序,可以调用fork函数新建一个进程,然后调用任何一个exec函数,看起来像是通过执行应用程序而产生了一个新进程。
    • Exit/_exit:终止进程。
    • Wait/waitpid:使父进程阻塞。
    进程间通信:
    使用较多的有:无名管道/有名管道、信号、消息队列、共享内存、信号量。
    • 有名管道:int mkfifo(const char *filename,mode_t mode)
    • 信号:在软件层面上对中断机制的一种模拟。一个完整的信号生命周期包括:信号产生、信号注册、信号注销、信号处理
    ○ 发送信号:kinll()、raise()
    ○ 捕获信号:alarm()、pause()
    ○ 处理信号:signal()
    • 共享内存:最有用的进程间通信方式,但需要依靠同步机制。
    • 消息队列:消息队列就是一个消息的链表
    Pthread线程库:
    • Pthread_creat:创建线程
    • Pthread_exit:函数终止
    • Pthread_join:等待一个特定的线程退出
    • Pthread_mutex_init:创建一个互斥量
    • Pthread_yield:主动释放CPU给其他进程
    信号量线程控制(PV原语):P减一,V加一,当小于0时,P停止。
    同步和互斥:

    CH11 ARM Linux网络开发实例

    TCP/IP
    应用层 Application layer
    传输层 Transport layer
    互联网层 Internet layer
    网络接口层 Network interface layer
    应用层在操作系统外部,传输层在操作系统内部,互联层在IP地址上,网络接口层在物理地址上。
    可靠性特性:IP协议不能保证IP保温传递的可靠性,但TCP协议面向连接的服务,体现了可靠性。
    TCP/IP的特点是将不同的底层物理网络、拓扑结构隐藏起来,向用户和应用程序提供通用、统一的网络服务,使用户看起来整个TCP/IP互联网就是一个统一的整体,它独立于具体的各种物理网络技术,能够向用户提供一个通用的网络服务。

    Socket
    在Linux系统下,用户通过socket接口进行网络编程操作。常见的socket有:
    • 流式(SOCK_STREAM):提供可靠的、面向连接的通信流,使用TCP协议,保证了数据传输的正确性和顺序性。
    • 数据报(SOCK_DGRAM):无连接,使用UDP。
    • 原始:功能强大但使用较为不便。
    traceroute程序实例
    在命令提示符下面直接使用tracert即可。可以获得到目标主机时中途经过的路由器信息(IP)。原理是UDP数据包&不同生存时间(TTL)的IP,当TTL为1时,经过一个路由器IP包即被抛弃,为2时可以经过两个路由器,以此类推,返回所有路由器的信息。

    CH12 嵌入式Linux设备驱动开发

    Linux中的设备驱动程序有如下特点:
    • 内核代码
    • 内核接口
    • 内核机制和服务
    • 可装载
    • 可设置
    • 动态性
    字符设备
    加载设备驱动模块:init_module()
    卸载:cleanup_modele()

    块设备
    块设备包括:IDE硬盘、SCSI硬盘、光驱等。
    每当用户进程对一个块设备发出一个读写请求时,首先调用块设备所公用的函数generic_file_read()和generic_file_write()。如果缓冲区有数据/缓冲区还可以存放数据,则进行数据的读/写。

    LCD驱动编写实例
    TFT显示屏:(Thin Film Transistor)即薄膜场效应晶体管。

    2022年8月29日

    书评

    前面讲的比较浅显,后面还好(或许可以理解成叫“由浅入深”),学过单片机或C语言的可以当做复习来用。书里面有错误,且在讲到或与非的时候举的例子还不如不举。。。
    再者,P163的例子,为了说明递归算法的用途,举了一个例子说除法取余数无法输出“1“”3“”2“”6“,那就分别/1000,/100%10,/10%10和%10啊,为什么非得用余数呢?这样的例子显示不出来后面递归算法的必要性。

  • 相关阅读:
    HTB-Sense
    小 A 的卡牌游戏(Gym - 103186B)
    PyTorch 中的乘法:mul()、multiply()、matmul()、mm()、mv()、dot()
    springboot如何返回中文json,保证顺序。LinkedHashMap应用实例
    Flink状态管理与检查点机制
    【Arduino TFT】Arduino uzlib库,用于解压gzip流,解析和风天气返回数据
    C++入门(二)
    C语言中 -> 和 . 的区别
    delete删除后怎么恢复文件?四种方法进行找回
    html5期末大作业:基于HTML+CSS技术实现——传统手工艺术雕刻网站(3页)
  • 原文地址:https://blog.csdn.net/callmeup/article/details/126587693