admin 管理员组

文章数量: 887021

目录

一、C语言基础知识

C语言编译机制

1、宏定义与条件编译

2、运算符

3、指针

5、数组

6、内存管理

7、函数:

8、关键字:

9、Linux 下的文件类型

二、嵌入式应用开发:

系统调用、库函数:

文件IO(系统IO):

文件描述符:

open、write、read、lseek、close:

​编辑

Linux下文件管理:

错误编号errno、strerror()、perror():

空洞文件:

文件描述符和文件的关系:

文件描述符的复制:

文件共享:

原子操作与竞争冒险:

截断文件 truncate():

fcntl()和ioctl():

标准IO:

标准I/O和文件I/O的区别:

fopen、fread、fwrite、fseek、ftell():

检查或复位状态:

格式化输出和输入:

I/O 内核缓冲:

stdio缓冲:

文件描述符与FILE指针互转:

文件属性和目录:

文件类型-7类:

获取文件属性-stat函数:

软链接和硬链接:

目录:

字符串处理

字符串输出/输入:

系统信息和系统资源:

系统信息和时间获取:

进程时间(cpu时间):

信号:

信号的分类:

进程对信号的处理:

向进程发送信号:

信号集:

信号掩码:

实时信号:

进程:

进程的相关概念:

进程相关的函数:

环境变量:

fork()创建子进程:

进程状态与进程关系:

守护进程:

进程间通信IPC:

通信目的:

如何通信:

通信方法:

1、管道

2、信号

3、内存映射

4、共享内存

5、信号量

6、socket套接字

7、消息队列

线程:

概念与特点:

创建线程:

终止线程:

回收线程:

取消线程:

分离线程:

线程同步:

互斥锁:

条件变量:

信号量:

网路编程:

基本概念知识:

UDP框架:

TCP框架:


一、C语言基础知识

1. C语言编译机制

包含预处理编译汇编链接四个环节。

1 $ gcc Hello.c // 使用gcc 编译 Hello.c 并且默认生成一个名为 a.out的可执行文件
2 $ gcc Hello.c  ‐o  haha  // 使用gcc 编译 Hello.c ,并且指定生成名字为haha 的可执行文件

编译原理:

机器码(二进制):是处理器能直接识别的语言,不同的机器码代表不同的运算指令,不同的处理器机器码不同,所以机器码不可移植。

汇编语言:是机器码的符号化,即汇编就是用一个符号来代替一条机器码,所以不同的处理器汇编也不一样,即汇编语言也不可移植。(汇编语言和机器码一一对应的)

C语言:在编译时我们可以使用不同的编译器,将C源码编译成不同架构处理器的汇编,所以C语言可以移植。

2. 宏定义与条件编译

    #define 是一个预处理命令,用来为一个字符串或者值定义一个符号常量。typedef是一个关键字,用于为已有类型定义新的类型别名。

    无参宏     例如:#define PI  (float)3.1415

    带参宏     例如:#define MAX(a,b)  a>b? a:b

    无值宏    例如:#define DE_BUG    //定义的时候不给值,只做“是否有定义”的判断用

 条件编译: 

 形式1:

    1 #ifdef      // 判断某一个宏是否有定义

    2

    3                 // 代码块

    4

    5 #endif      // 判断语句的结束

 形式2:

    #if MACRO   // 只要判断MACRO为非零值则表示条件为真

            printf("__%s__%s__%d__\n", __FUNCTION__ , __FILE__ , __LINE__ );

    #endif

    .h 头文件格式:

    1 #ifndef  DEMO_H     // 判断是否有定义某个宏 ,用来防止头文件被多次包含出现的重定义问题

    2 #define  DEMO_H    //如果没有定义,说明前面没有包含过该头文件

    3

    4 //其它头文件

    5 ....

    6 // 结构体声明

    7....

    8 // 函数声明

    9 ..

    10 #endif     // ifndef 的结束

要检查a是否定义:

#if defined  a  //检验a是否被定义

#undef  a     //用#undef语句解除定义

#define  a  200   //重新定义a为200

‘’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’

#ifndef  a    //如果a没有被定义,  #ifndef是if not defined的缩写

#define  a  100

#endif

 作用:表明这是C代码,要按照C语言的规则去解读,而不是按照C++的规则去解析,这样就可以保证C++代码正确的调用C代码。

#ifdef __cplusplus

extern "C"{

#endif

xxxxx //C语言的n个函数实现

#ifdef __cplusplus

}

#endif

3. 运算符

算数运算符:

运算符

术语

示例

结果

+

正号

+3

3

-

负号

-3

-3

+

10 + 5

15

-

10 - 5

5

*

10 * 5

50

/

10 / 5

2

%

取模(取余)

10 % 3

1

++

前自增

a=2; b=++a;

a=3; b=3;

++

后自增

a=2; b=a++;

a=3; b=2;

--

前自减

a=2; b=--a;

a=1; b=1;

--

后自减

a=2; b=a--;

a=1; b=2;

赋值运算符:

运算符

术语

示例

结果

=

赋值

a=2; b=3;

a=2; b=3;

+=

加等于

a=0; a+=2;

a=2;

-=

减等于

a=5; a-=3;

a=2;

*=

乘等于

a=2; a*=2;

a=4;

/=

除等于

a=4; a/=2;

a=2;

%=

模等于

a=3; a%=2;

a=1;

逻辑运算符:与&&    或||    非!

4. 指针

内存地址(编号):

编码就是对内存的每一个字节(8位)分配一个32位或64位的编号(与32位或者64位处理器相关)。 这个内存编号我们称之为内存地址。

 char:占一个字节分配一个地址

 int: 占四个字节分配四个地址

指针概念:

 指针的实质就是内存“地址”。指针就是地址,地址就是指针。

 指针是内存单元的编号,指针变量是存放地址的变量。

 指针也是一种数据类型,指针变量也是一种变量。

l指针变量指向谁,就把谁的地址赋值给指针变量。

l“*”操作符操作的是指针变量指向的内存空间。取目标。

&可以取得一个变量在内存中的地址,但不能取寄存器变量。

使用sizeof()测量指针的字节大小,得到的总是:4(32位系统)或8(46位系统)

指针变量加减:如果是一个int *,+1的结果是增加一个int的大小

int a = 10;

int * p = &a; //一级指针,p是指针变量

*p = 100;   //*p<==>a, a=100

二级指针

int **q = &p;//可理解为int *(*q) = &p   *q就是p    //**q就是a

定义

说明

int  i

定义整形变量

int *p

定义一个指向int的指针变量

int a[10]

定义一个有10个元素的数组,每个元素类型为int

int *p[10]

定义一个有10个元素的数组,每个元素类型为int*

int func()

定义一个函数,返回值为int型

int *func()

定义一个函数,返回值为int *型

int **p

定义一个指向int的指针的指针,二级指针

数组指针:专门用来指向数组的指针。

int (* p) [5] ;//定义一个名为p的指针,指向的类型为int  [5]

指针数组:专门用来存放指针的数组。

int * p[5] ; //定义一个名字为p的数组,并且确定该数组中用来存放int *整形地址。

指针与数组:

int arr[5] = {1,2,3,4,5};

int *p1 = &arr[0] ;  // 等价与int *p2= arr;指针p指向数组的首地址。(数组名arr只代表数组首元素的地址)

指针的万能拆解方法:

对于任何的指针都可以分为两部分:

第一部分:说明他是一个指针 (*p)

第二部分:说用它所指向的内容的类型   (*p)以外的东西

void 型指针:是没有办法直接索引目标的。必须先进行强制类型转换在使用。

修饰指针,表示该指针指向了一个未知类型的数据。

修饰函数的参数列表, 则表示该函数入参不需要参数。

修饰函数的返回值,则表示该函数没有返回值。

 

const指针:

常指针:char * const p; // p指针变量无法修改,p指针指向地址的内容可以变化。

常目标指针:修饰的是指针所指向的目标,表示无法通过该指针来改变目标的数据。

char const * p ;  等价于 const char * p ;

5. 数组

        只有在定义的时候赋值,才可以称为初始化。数组只有在初始化的时候才可以统一赋值。

 

6. 内存/外存

  1. RAM(Random Access Memory,随机存取存储器)
    • 定义:RAM是一种易失性存储器,能够随机访问存储在其中的数据。当电源关闭时,RAM中的数据会丢失。
    • 分类
      • SRAM(Static Random Access Memory,静态随机存取存储器):不需要刷新电路即能保存数据,存取速度快,但价格较高,通常用于CPU的缓存。
      • DRAM(Dynamic Random Access Memory,动态随机存取存储器):需要定期刷新以维持数据,存取速度相对较慢,但价格较低,是计算机内存的主流。DRAM进一步分为SDRAM、DDR SDRAM等。
  2. ROM(Read-Only Memory,只读存储器)
    • 定义:ROM是一种非易失性存储器,数据在制造时写入,之后通常不能更改。
    • 注意:传统上ROM是只读的,但现代技术如Flash等已经模糊了ROM和RAM之间的界限。nand flash已代替ROM。
  3. Flash
    • 定义:Flash是一种非易失性存储器,掉电不丢失,结合了RAM和ROM的优点。
    • 分类
      • NOR Flash:适用于小容量、快速随机访问的场景,如BIOS、路由器系统等。
      • NAND Flash:写入性能好,大容量下成本低,广泛应用于手机、SSD等。NAND Flash进一步分为SLC、MLC、TLC等类型,根据每个存储单元存储的比特数不同而区分。存储器结构由块(Block)和页(Page)组成,每个块包含多个页。擦除操作以块为单位进行。
      • eMMC是一种集成了闪存芯片和控制器的储解决方案,其核心是NAND Flash。

内存:内部存贮器,暂存程序/数据——掉电丢失 SRAM、DRAM、DDR、DDR2、DDR3。内存是沟通CPU与硬盘的桥梁:暂存放CPU中的运算数据;暂存与硬盘等外部存储器交换的数据。

 外存:外部存储器,长时间保存程序/数据—掉电不丢ROM、ERRROM、FLASH(NAND [eMMc]、 NOR)、硬盘、光盘。

大端序(Big-Endian)和小端序(Little-Endian):是两种计算机存储字节序的方式。

在计算机中,数据的存储是以字节为单位的,每个字节由8个二进制位组成。对于多字节的数据类型(如整数、浮点数等),计算机需要决定如何将其存储在内存中。

大端序是指将数据的高位字节存储在低位地址,低位字节存储在高位地址。小端序则相反,将数据的低位字节存储在低位地址,高位字节存储在高位地址。(网络字节序指的就是大端序)

例如: 数字int  258的16进制形式为0x00000102,二进制形式为:00000000 00000000 00000001 00000010,其在计算机内存中的小端序存储方式如图所示:

7. 函数:

静态函数:缩小可见范围,减少与其它文件中同名函数冲突的问题。静态函数一般会定义在头文件中, 然后被需要使用该函数的源文件包含即可。

    1 static int func (int a, int b )

    2 {

    3  // 函数体

    4 }

递归函数:概念: 如果一个函数的内部,包含了对自己的调用则称为递归函数。

8. 关键字:

static:

    静态变量都在全局数据区分配内存。静态全局变量在声明它的整个文件都是可见的,而在该文件之外是不可见的。

    局部变量所在函数每次调用的时候都会被重新分配存储空间,函数结束后,就会回收该存储空间。静态局部变量不会,始终保持当前值。

extren:

    用来声明全局变量的,表明变量或者函数是定义在其他其他文件中的。用来修饰外部全局变量,表示该变量在其他文件中定义。首先讲一下,

    声明定义概念:声明不等于定义,声明只是指出了变量的名字,并没有为其分配存储空间;定义指出变量名字同时为变量分配存储空间,定义包含了声明。注意:在程序中一个变量可以声明多次,但只能定义一次。

volatile:

    提醒编译器它后面所定义的变量随时都有可能改变,因此编译后的程序每次需要存储或读取这个变量的时候,告诉编译器对该变量不做优化,都会直接从变量内存地址中读取数据,从而可以提供对特殊地址的稳定访问。如果没有volatile关键字,则编译器可能优化读取和存储,可能暂时使用寄存器中的值,如果这个变量由别的程序更新了的话,将出现不一致的现象。(简洁的说就是:volatile关键词影响编译器编译的结果,用volatile声明的变量表示该变量随时可能发生变化,与该变量有关的运算,不要进行编译优化,以免出错)。

    在嵌入式系统编程中,将变量声明为volatile通常用于处理硬件寄存器或在不同线程或中断服务例程(ISR)之间共享的变量。它确保编译器不会优化掉对该变量的某些操作,确保其值始终从内存中读取而不是从缓存中读取,并且对变量的写入立即对代码的其他部分可见。

9. Linux 下的文件类型

1,普通文件(regular):存在于外部存储器中,用于存储普通数据。

2,目录文件(directory):用于存放目录项,是文件系统管理的重要文件类型。

3,管道文件(pipe):一种用于进程间通信的特殊文件,也称为命名管道 FIFO。

4,套接字文件(socket):一种用于网络间通信的特殊文件。只能使用系统io访问。

5,链接文件(link):用于间接访问另外一个目标文件,相当于 Windows 快捷方式。

6,字符设备文件(character):字符设备在应用层的访问接口。只能使用系统io访问。

7,块设备文件(block):块设备在应用层的访问接口

系统IO (没有设置缓冲区)

标准库IO

目录操作

库文件

10



 

二、嵌入式应用开发:

        通过调用内核提供的系统调用函数或使用C 库函数来开发具有相应功能的应用程序。

系统调用、库函数:

系统调用:Linux操作系统向应用层提供的接口,是Linux应用层进入内核空间的入口。比如open、write、read、close 等。

C库函数:一部分是对系统调用函数封装而来,在效率和使用便利性方面有提升。一部分是为调用系统函数的而编写的函数。

区别:

  1. 库函数是属于应用层,而系统调用是内核提供给应用层的编程接口,属于系统内核的一部分;
  2. 库函数运行在用户空间,调用系统调用会由用户空间(用户态)陷入到内核空间(内核态);
  3. 库函数通常是有缓存的,而系统调用是无缓存的,所以在性能、效率上,库函数通常要优于系统调用;
  4. 可移植性:库函数比系统调用具有更好的可移植性。通常对于不同的操作系统,其内核向应用层提供的系统调用往往都是不同,譬如系统调用的定义、功能、参数列表、返回值等往往都是不一样的。

文件IO(系统IO):

文件描述符:

概念:文件描述符是文件句柄、一个非负整数 int类型,与对应的文件相绑定。

如何分配:每次分配一个没有被使用的最小非负整数作为文件描述符。如3、4等等。(其中0、1、2分别被标准输入、标准输出和标准错误占用了 )

open、write、read、lseek、close:

#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>

打开文件或新建并打开
int open(const char *pathname, int flags);  
int open(const char *pathname, int flags, mode_t mode);//创建文件时才传入mode-权限。
//返回值:打开失败 -1
//O_TRUNC标志:丢弃文件中的内容,文件大小变为0
//O_APPEND标志:保证每次调用 write()时都是从文件末尾开始写入。对read操作不会有影响。


把buf写入到文件描述符:
ssize_t write(int fd, const void *buf, size_t count);
//返回值:成功:返回写入的字节数      失败:-1

读取到buf:
ssize_t read(int fd, void *buf, size_t count);

关闭文件:
int close(int fd);  //成功:0     失败:-1

调整读写位置偏移:
int lseek

本文标签: 语言