【华清远见嵌入式培训】C高级

嵌入式系统

以计算机应用为中心,软硬件可裁剪,对功耗、体积、性能等都有一定要求的专用计算机系统,是一种基于Linux内核(不包含GNU组件)的自由及开放源代码的操作系统(HarmonyOS是微内核

  • 对于PC电脑来说:硬件 + BIOS(第一段启动代码:初始化部分硬件(USB驱动))+ win内核+根文件系统

  • 对于嵌入式硬件来说:硬件+uboot+Linux内核+根文件系统(Linux)

Linux体系架构

  • 内核

  • 进程管理进程调度控制进程对CPU的访问。当需要选择下一个进程运行时,由调度程序选择最值得运行的进程。可运行进程实际上是仅等待CPU资源的进程,如果某个进程在等待其它资源,则该进程是不可运行进程。Linux使用了比较简单的基于优先级的进程调度算法选择新的进程。

  • 内存管理:Linux用了称为“虚拟内存”的内存管理方式,Linux将内存划分为容易处理的“内存页”(对于大部分体系结构来说都是 4KB)。Linux 包括了管理可用内存的方式,以及物理和虚拟映射所使用的硬件机制

  • 设备管理:是 Linux 内核的主要部分

  • 文件管理:Linux 操作系统将独立的文件系统组合成了一个层次化的树形结构,并且由一个单独的实体代表这一文件系统。虚拟文件系统(VirtualFileSystem,VFS)是 Linux 内核中非常有用的一个方面,因为它为文件系统提供了一个通用的接口抽象。

  • 网络管理:提供了对各种网络标准的存取和各种网络硬件的支持。网络接口可分为网络协议和网络驱动程序。网络协议部分负责实现每一种可能的网络传输协议。Linux内核的网络部分由BSD套接字、网络协议层和网络设备驱动程序组成。

  • shell命令:shell是系统的用户界面,提供了用户与内核进行交互操作的一种接口。它接收用户输入的命令并把它送入内核去执行,是一个命令解释器。

  • 操作系统连接用户使用的软件和电脑硬件

  • 内核是操作系统的一部分,操作系统分为内核和系统调用两种

  • 操作系统其实就是一种软件, 让电脑硬件为我们所用

  • shell是壳, 它是一个软件, 它提供了一个用户操作系统的一个接口, 通过shell我们可以调用其他软件,比如chmod chown firefox man 等,它可以调用内核来工作。shell其实就是一个接口;狭义的shell 指的是命令行界面,比如我们的bash;广义的shell 则包括图形界面的软件,因为其实图形界面也可以调用系统内核

  • shell、内核、硬件、用户之间的关系:

1. 用户从命令行提示终端输入命令或者按键,提交给shell。

2. shell将命令转换为内核可以识别的指令。

3. 内核驱动硬件设备实现对应指令功能,将执行结果提交给shell。

4. shell将反馈的结果解释提交给用户识别。

  • 文件系统:Linux 系统能够支持的文件系统非常多,除 Linux 默认文件系统 Ext2、Ext3 和 Ext4 之外,还能支持 fat16、fat32、NTFS(需要重新编译内核)等 Windows 文件系统。

1)磁盘文件系统:硬盘、U盘

文件系统的格式有:ext、ext2、ext3、ext4、vfat、fat32...

2)网络文件系统:nfs服务、samba

3)虚拟文件系统:tmpfs

  • 应用程序

Linux常识性知识

  1. 硬链接和软连接

  1. 硬链接:根据linux系统分配给文件的inode号(物理编号:ls -i)进行建立的,硬链接只能在同一文件系统中的文件之间进行链接没办法跨越文件系统,硬链接可由命令link 或 ln 创建

格式:ln 被链接的文件(源文件) 生成的链接文件(目标文件)
	 link 被链接的文件(源文件) 生成的链接文件(目标文件) ———— 推荐记忆,见名知意

硬链接的属性:

  • 相当于生成一个副本,起别名

  • 源文件删除链接依然存在

  • 不能链接目录

  • 修改内容都变化

  1. 软连接:ln -s (符号链接) 利用文件的路径名来建立的,最好从绝对路径开始(增加可移植性)

格式:ln -s 被链接的文件(源文件) 生成的链接文件(目标文件)

软链接的属性:

  • 相当于快捷方式

  • 源文件删除,链接断开,建立源文件之后重新链接

  • 软链接可以链接目录

  • 修改内容都变化

变量的内存分配

kernel(内核)

环境变量

命令行参数

栈(由 系统自动开辟与释放空间 (局部变量或函数参数))

mmap(共享库的内存映射区域)

堆(由 程序员手动开辟(malloc)与释放(free)的空间 ,属于动态申请)

.bss(未初始化的全局变量及静态变量)

.data(已初始化的全局变量和静态变量)

常量区( 字符串 常量)

.text(代码段)

在C/C++中内存分为5个区,分别为栈区、堆区、全局/静态存储区、常量存储区、代码区

  1. 栈区(stack):指那些由编译器在需要的时候分配,不需要时自动清除的变量所在的储存区,如函数执行时,函数的形参以及函数内的局部变量分配在栈区,函数运行结束后,形参和局部变量去栈(自动释放)。栈内存分配运算内置与处理器的指令集中,效率高但是分配的内存空间有限。

  1. 堆区(heap):指哪些由程序员手动分配释放的储存区,如果程序员不释放这块内存,内存将一直被占用,直到程序运行结束由系统自动收回,c语言中使用malloc,free申请和释放空间。

  1. 静态储存区(static):全局变量和静态变量的储存是放在一块的,其中初始化的全局变量和静态变量在一个区域,这块空间当程序运行结束后由系统释放。

  1. 常量储存区(const):常量字符串就是储存在这里的,如“ABC”字符串就储存在常量区,储存在常量区的只读不可写。const修饰的全局变量也储存在常量区,const修饰的局部变量依然在栈上。

  1. 程序代码区:存放源程序的二进制代码。

局部变量的使用

#include
char *fun(){
    char str[] = "hello";
    return str;
}
int main(){
    char *p = fun();
    printf("%s\n",p); // 此时打印NULL,如何打印helloworld?
    return 0;
}
  1. static修饰函数内的str使其延长生命周期

static str[] = "hello"
  • static在函数内部声明的内部静态变量,只需初始化一次,而且变量存储在 全局数据段(静态存储区) 中,而不是栈中

  • static的本质是延长变量或函数的生命周期,同时限制其作用域。

  • static声明的全局变量、函数,仅当前文件内可用,其他文件不能引用。

  • static定义的变量存放在.bss段,未初始化前初值为0;

C语言规定:在变量作用域重叠时,作用域为小范围的变量覆盖大范围的变量

  1. auto修饰的变量:初值随机,存放在栈区。

Linux命令进阶

1. 软件安装管理工具

  1. dpkg apt (Debian Packager——Debian打包程序的缩写)

需要软件的安装包存在,不能从镜像站点获取软件包,不能检测软件之间的依赖关系,安装不需要网络。

sudo dpkg -i 完整的软件包名:安装软件
sudo dpkg -r 软件名 :卸载
sudo dpkg -s 软件名 :查看安装状态
sudo dpkg -P 软件名 : 完全卸载
sudo dpkg -L 软件名 :列出软件文件清单
  1. apt

安装软件不需要软件的安装包存在,能从镜像站点获取软件包,能检测软件之间的依赖关系,安装需要网络。

apt-get:
    sudo apt-get install 软件名——下载安装软件
    sudo apt-get remove 软件名——卸载软件包
    sudo apt-get --purge remove 软件名——完全卸载
    sudo apt-get update——更新软件源
    sudo apt-get upgrade——将所有的软件更新为最新版本	   
    sudo apt-get clean——清除下载的软件包
	位置:/var/cache/apt/archives	
apt-cache命令:查看安装状态、依赖关系
	sudo apt-cache show 软件名——获取二进制软件包的详细描述信息
	sudo apt-cache policy 软件名——查看安装状态
	sudo apt-cache depends 软件名——我依赖哪个软件
	sudo apt-cache rdepends 软件名——哪个软件依赖我

2. Linux支持的信号

Linux底层收到信号后,对进程进行一些动作(终止(杀死))

#include 
#include 
void fun(int sig)
{
    printf("Signal:ctrl + c\n");
}

int main(int argc, const char *argv[])
{

    //signal(int signum, void (*handler)(int));

    signal(SIGINT,fun);

    while(1);
    return 0;
}

使用命令

ps -aux

来查看当前所有进程情况(进程号,时间等信息)

进而可以使用kill命令杀死指定进程

最常用的信号是:

  • 1 (HUP):重新加载进程。

  • 9 (KILL):杀死一个进程。

  • 15 (TERM):正常停止一个进程。

实例:

  • 杀死进程

# kill 12345
  • 强制杀死进程

# kill -KILL 123456
  • 发送SIGHUP信号,可以使用一下信号

# kill -HUP pid
  • 彻底杀死进程

# kill -9 123456

3.GDB调试

  1. 在用GDB调试之前确定代码没有语法错误。

  1. 设置断点的时候,必须让主函数运行起来

  1. 第一次断点设置,设置在主函数当中的某一行,这样编译器才能从入口函数进来

  1. 在gcc编译选项中一定要加入‘-g’

  1. 只有在代码处于“运行”或“暂停”状态时才能查看变量值

  1. 设置断点后程序在指定行之前停止

gcc -g test.c
gdb a.out

(gdb)l:列出源文件内容
(gdb)b 10:设置断点在第10行
(gdb)r :运行(设置断点后一定要先运行,才能进行单步调试往下)
(gdb)n:单步调试(断点行是不被运行的,n单步调试的时候不进子函数)
(gdb)s:单步运行(断点行是不被运行的,s单步调试的时候进子函数)
(gdb)p 变量名 :查看变量值

4.printf输出调试

前提是不出现语法错误和低级逻辑错误,大概定位错误

printf("%s,%s,%d\n",__FILE__,__func__,__LINE__);
// 输出文件名,函数名,语句所在行数
  • DEBUG的本质就是去定位bug,GDB的好处是可以监视变量,而printf用于大型代码段且不需过分关心变量值时

5.解压和压缩tar

linux文件归档的意思是为文件或目录备份,建立归档文件,在进行压缩和解压缩后都会对源文件进行归档

压缩: tar -cvzf     file.tar.gz(压缩文件名)     *.c(被压缩的文件,*代表所有名称匹配的文件)
解压: tar -xvf   xxxx.tar.gz
	  unzip xxxx.zip (win下的压缩包在Linux下解压缩)

大部分文件不可在win下解压在Linux内使用

可以使用命令对win下的压缩包进行解压:

unzip 文件名

6.进程相关命令

  • 进程:就是程序的一次执行过程。(动态)

  • 程序:在磁盘空间上存放的可执行的二进制文件。(静态)

  1. ps命令

ps -aux // 查看进程号

用户

PID

TTY(CPU占用率)

运行是否依赖终端

状态

名称

R+(前台运行)

R(后台运行)

ps -ef // 查看进程号,包含父进程的进程号PPID

ps -ajx // 可以查看父进程id,组id,会话id
	多个进程可以组成一个组,多个组可以组成一个会话,多个会话可以组成一个会话组。
	分级会话、组、父进程的目的是分工明确,管理方便
./a.out &  // 将a.out在后台运行起来
	Ctrl+z:将前台运行的进程暂停同时放到后台
	bg 数字(这里的数字为你按Ctrl+Z的时候前面中括号里面的数字):将后台暂停的进程在后台跑起来。
	fg 数字 //将后台运行的进程拉到前台运行  
  1. jobs命令

jobs // 查看后台运行的程序
  1. top命令

top // 动态查看进程的状态(动态看进程占用CPU)
  翻页    shift + >    shift + <
  PR     PR=NI+20
  NI     优先级范围:  +19 ~ -20   (值越小优先级越高)
  1. nice命令

nice 以指定优先级运行进程

sudo nice -num ./可执行程序 //以优先级为num运行程序
eg.
sudo nice -3 ./可执行程序   //程序以3优先级运行
sudo nice --3 ./可执行程序  //程序以-3优先级运行
  1. renice命令

运行进程优先级修改

sudo renice num PID 
eg.
 eg:
sudo renice 3 PID   //PID对应的进程优先级改为3
sudo renice -3 PID	//PID对应的进程优先级改为-3
  1. kill命令

给进程发送信号

kill -l // 查看linux中的信号
	SIGUSR1 SIGUSR2 未定义功能的信号,可以自定义去设置其功能
	项目异步方式(不阻塞等待)可以使用上述信号
kill -信号编号 PID // 给指定进程发送指定信号
killall a.out // 杀死所有名字为a.out

7. shell的特殊字符

  1. 通配符

*:匹配任意长度字符,通常用于省略一些个体差异,从而去匹配一些文件普遍名称字符

  1. 管道

|:把前一个命令的输出作为下一个命令的输入传入

eg.

ls | sort # 将ls的输出(展示当前目录下可见文件)当作sort(将输入进行排序)的输入
  1. wc命令

wc -l 文件名 # 查看文件的行数
wc -w 文件名 # 查看文件单词个数
wc -c 文件名 # 文件字符个数
wc -m 文件名 # 文件大小
  1. grep(Globally search a Regular Expression and Print)命令:

查询文件中的字符串

grep "字符串" 文件名  # 输出文件中给定字符串所在行
grep -n "字符串" 文件名 # 显示行号
  1. 输入输出重定向

重定向:是指不使用系统提供的标准输入端口,而进行重新的指定,重定向简单理解就是使用 “<”符来修改标准输入设备

eg.

cat /etc/passwd  #它是以键盘作为标准输入设备,并将文件内容显示到控制台
cat < /etc/passwd #它是将passwd文件指定为输入设备,并将内容显示到控制台
cat << delimiter #它的作用是将这个 delimiter 字符之前的内容(document) 作为输入传递给 cat
cat /etc/passwd < a > b.txt #将文件passwd输入重定向到a,输出重定向到b.txt文件
  1. 命令置换

``(Esc键下的):将一个命令的输出作为另一个命令的参数。

eg.

echo "user is `whoami`"  #显示user is Linux
  1. printenv:显示Linux下自带的环境变量

8. shell基本命令

  1. man命令

man man # 查看man手册功能

第一章节:shell命令

第二章节:系统调用

第三章节:C库(库函数)

  1. sudo passwd 用户名:修改用户密码

  1. su 切换用户

su             # 默认切换到root
sudo su 用户名  #切换到指定用户
exit           # 退出切换的用户   
  1. date 查看系统的日期

9. 文件系统命令

多文件编译

整个工程包含:一个main.c和若干file.c文件头文件

!!make工具!!

  1. 工程管理器,顾名思义,是指管理较多的文件,Make工程管理器也就是个“自动编译管理器”,这里的“自动”是指它能构根据文件时间戳自动发现更新过的文件而减少编译的工作量,同时,它通过读入Makefile文件文件的内容来执行大量的编译工作。

  1. make工具的作用

当项目中包含多个c文件,但只对其中1个文件进行了修改,那用gcc编译会将所有的文件从头编译一遍,这样效率会非常低;所以通过make工具,可以查找到修改过的文件(根据文件时间戳),只对修改过的文件进行编译,这样大大减少了编译时间,提高编译效率

  1. Makefile是Make读入的唯一配置文件

  1. Makefile的编写格式

#目标:依赖
# 命令
add:add.o main.o
	gcc add.o main.o -o add
add.o:add.c
	gcc -c add.c -o add.o
main.o:
	gcc -c main.c -o main.o
.PHONY:clean
clean:
	rm *.o add
#目标实现,需要通过依赖文件实现可以只有目标没有依赖。
  1. .PHONY: clean ( 有目标没有依赖,make执行Makefile文件内的指令,执行"make"默认执行的是第一个目标对应的命令。若想执行剩余的目标,需要 "make 目标"执行 )

声明一个伪命令。若之前的目标中有生成可知执行文件名为clean。执行“make clean”时,make会认为clean目标已实现。不执行clean目标对应的指令,**用.PHONY声明后,make认为clean是一个指令,

“make clean”时会执行clean指令对应的命令。**

  1. 变量

$HOME ($:表示取值)

(1)预定义变量

	CC     #默认值为cc,与gcc同
	RM     #默认值为rm -f 
	CFLAGS #无默认值,一般为c编译器的选项
	OBJS   #一般为目标文件xx.o
	
  1)机器上面运行的是源码?还是可执行文件?
		可执行文件
  2)在虚拟机上面编写代码,在虚拟机上面执行代码
	(编写代码和执行代码在同一个架构上)
  3)架构:
	 win:X86 
	 公司的产品:ARM架构;
  4)在X86架构上编写和编译代码,在ARM架构上执行代码,交叉开发(编写代码、编译代码和执行代码的架构不一样)编译代码的时候要使用交叉编译工具链去编译代码
(2)自动变量:
	$<	第一个依赖文件的名称
	$@  目标文件的完整名称
	$^	所有不重复的目标依赖文件,以空格分开
这些是默认的,不需要提前声明

如何书写一个C工程的makefile文件?

  • 目标文件/可执行文件是谁?目标的依赖是谁?

  • 需不需要伪命令去执行一些其他操作?

eg. 有一个简单的helloworld项目目录如下

helloworld:

header.h

file1.c

file2.c

请编写一个Makefile文件,file1.c和file2.c文件都包含了header.h。

分析:

  • 生成的file文件为可执行文件,其依赖文件为file1.o和file2.o

  • file1.o和file2.o依赖文件分别是file1.c和file2.c

  • 需要伪命令去删除.o文件和可执行文件

CC=gcc
OBJS= file1.o file2.o
CFLAGS: -c -O -g

file:file1.o file2.o
	$(CC) $(OBJS) -o file1
file1.o:file1.c
	$(CC) -c file1.c -o file1.o
file2.o:file2.c
	gcc -c file2.c -o file2.o
.PHONY:clean
clean:
	rm *.o file
上述makefile更新:(以自动变量)
CC=gcc
OBJS= file1.o file2.o
CFLAGS: -c -O -g

file:$(OBJS)
	$(CC) $(OBJS) -o $@
$(OBJS):%.o:%.c #所有的.o文件依赖于.c文件
	$(CC) $(CFLAGS) $^ -o $@
	
.PHONY:clean
clean:
	rm *.o file

条件编译

编译器根据条件的真假决定是否编译相关的代码,可以用于调试,最常见的条件编译是防止重复包含头文件的宏

  1. 根据宏是否定义,其语法如下:

#ifdef  
	……
#else
	……
#endif

eg.

#include 
#define N
int main(){
#ifdef N
    printf("Hello world!"); // 将被输出
#else
    printf("Weclome to China!"); // 不被输出
#endif    
    return 0;
}
  1. 根据宏的值是否为真,其语法如下:

#if  
	…
#else 
	…
#endif

eg.

#include 
#define N 1
int main(){
#if N
    printf("Hello world!"); // 将被输出
#else
    printf("Weclome to China!"); // 不被输出
#endif
    return 0;
}
  1. 根据宏是否定义,作用:防止头文件重复包含,主要写在头文件里

#ifndef   
#define   
	....
#endif

eg.

#include 
#define N
int main(){
#ifndef N
    printf("Hello world!");
#else
    printf("Weclome to China!");
#endif
    return 0;
}

你可能感兴趣的:(C语言基础,数据结构,笔记,c++,嵌入式硬件,算法)