嵌入式编程学习笔记(一)

  • 学习地址:bilibili
  • 课程名称:《嵌入式之Linux下文件I/O精讲》-华清远见出品-曾老师
  • 课程链接

20200210-20200221

文件基础

概念
  • 一组相关数据的有序集合
文件类型

Linux中7中不同的文件类型,不同的操作系统所支持的文件类型是不同的

  • 常规文件 r
  • 目录文件 d
  • 字符设备文件 c
  • 块设备文件 c
  • 管道文件 p
  • 套接字文件 s

管道文件和套接字文件都是进程间通信的一种机制;套接字既可以进行网络通信也可以用于本地通信

  • 符号链接文件 l

文件I/O介绍

裸机硬件系统:如单片机,没有操作系统,代码可以直接操作物理硬件,通过地址操作
有操作系统设备:
标准IO通过内部的缓冲机制来减少系统调用的次数
Linux系统中I/O操作有两种方法:一种是标准I/O,另一种时文件I/O

标准I/O与文件I/O对比
  • 标准I/O遵循的是C的标准-ANSI
  • 标准I/O是带缓冲的流,系统会分配缓冲区;首先操作的是缓冲区,减少系统调用的次数
  • 标准I/O通过流(FILE)来表示打开的文件

  • 文件I/O遵循的是POSIX的标准(POSIX是操作系统的规范)
  • 文件I/O是无缓冲的, 每次的读写都是相应的系统调用去读写实际的文件
  • 文件I/O通过文件描述符(fd )来表示打开的文件
标准I/O流
FILE
  • 就是FILE结构体 FILE:
  • 标准I/O用一个结构体类型来存放打开的文件的相关信息
  • 标准I/O的所有操作都是围绕FILE来进行的
流(stream)
  • FILE又被称为流
  • 文本流/二进制流

Windows区分文本流和二进制流
Linux不区分,实际上都是二进制流

流的缓冲类型

标准I/O是带缓冲的I/O;先对缓冲进行操作

  • 全缓冲

当流的缓冲区无数据或无空间时才执行I/O操作

  • 行缓冲

当在输入和输出中遇到换行符(’\n’)时,进行I/O操作
eg:当流和一个终端相关联时,就是典型的行缓冲

  • 无缓冲

数据直接写入文件,流不进行缓冲

打开流

下列的函数用于打开一个标准的I/O流

FILE *fopen(const char *path,const char *mode);

成功时返回流的指针,错误时返回NULL

mode参数

mode参数
打开一个标准I/O的六种不同方式
六种不同方式

新建文件权限
  • fopen创建的文件访问权限是666(rw-rw-rw-
  • linux系统中umask设定会影响文件的访问权限,其规则为(0666&~umask
  • 用户可以通过umask函数修改相关设定
  • 如果不希望umask影响我们文件的访问权限,可以将umask设定为0
处理错误信息
关闭流
int fclose(FILE *stream)
  • fclose()调用成功后返回0,失败返回EOF,并设置error
  • 流关闭时自动刷新缓冲区中的数据并释放缓冲区
  • 当一个程序正常终止时,所有打开的流都会被关闭

建议当流不使用的时候及时主动关闭

  • 流一旦关闭就不能执行任何操作
读写流

流支持不同的读写方式

  • 读写一个字符:fgetc()/fputc()一次读/写一个字符
  • 读写一行:fgets()/fputs()一次读/写一行,一般处理文本流
  • 读写若干个对象:fread()/fwrite()每次读写若干个对象,而每个对象具有相同的长度,既可以处理文本流也可以处理二进制流
按字符输入

下列函数用来输入字符

#include<stdio.h>
int fgetc(FILE *stream);
int getc(FILE *stream);
int getchar(void);

成功时返回读取到的字符;若到文件末尾或出错时返回EOF
fgetc(stdin)等同于getchar()

按字符输出

下列函数用来输出字符

#include<stdio.h>
int fputc(int c,FILE *stream);
int putc(int c,FILE *stream);
int putchar(int c);

成功时返回写入的字符;出错时返回EOF
putchar()默认向标准输出流写入一个字符
fputc(int c,stdout)等同于putchar()
diff -ruN srcfile1 desfile2 可以比较差别

按行输入

下面的函数用来输入一行

#include<stdio.h>
char *gets(char *s);
char *fgets(char *s,int size,FILE *stream);

gets()从标准输入流读取一行放到以s为首地址的内存缓冲区中;不建议使用,容易造成缓冲区溢出,不安全
fgets()函数从指定流中读取size长度字符到以s为首地址的内存缓冲区中
成功时返回缓冲区的首地址s;到文件末尾或错误时返回NULL
遇到'\n'或已输入size-1个字符时返回,总是在指定缓冲区末尾包含'\0'
若先遇到'\n',则结尾是'\n''\0'
若先满足size-1,则结尾是'\0'

按行输出

下列函数用来输出字符串

#include<stdio.h>
char puts(const char *s);
char fputs(const char *s,FILE *stream);

puts()以s为首地址的内存缓冲区中放到标准输入流
puts()将缓冲区s中的字符串输出到stdout,并追加'\n'
fputs()函数从以s为首地址的内存缓冲区中写入到指定的流中
fputs()将缓冲区s中的字符串输出到stream
成功时返回输出的字符个数;错误时返回EOF

按指定对象输入

下列函数用于从流中读取若干个对象

#include<stdio.h>
size_t fread(void *ptr,size_t size,size_t n,FILE *fp);
size_t fwrite(const void *ptr,size_t size,size_t n,FILE *fp);

成功时返回实际读写的对象的个数;错误时返回EOF
fread()如果读到文件末尾返回的是0
既可以读写文本文件;也可以读写数据文件

按指定对象输出
流的刷新

全缓冲:当缓冲区满时进行刷新数据到流
行缓冲:当缓冲区满或者遇到'/n'进行刷新数据到流

下列函数用来主动刷新流

#include<stdio.h>
int fflush(FILE *fp);

成功时返回0;出错时返回EOF
将流的缓冲区中的数据写入实际的文件
linux下只能刷新输出缓冲区到文件

流的定位

每个流在打开的时候都会记录一个读写位置;流的定位实际上和这个读写位置有关

ftell/fseek/rewind
#include<stdio.h>
//获取当前流的读写位置
long ftell(FILE *stream);	
//设定流的当前读写位置whence是基准点,offset相对于基准点的偏移
long fseek(FILE *stream,long offset,int whence);	
//直接把流定义到起始位置
void rewind(FILE *stream);

ftell()成功时返回的时流的当前读写位置;出错时返回EOF(-1)
fseek()定位一个流,成功时返回0;出错时返回EOF(-1)
whence参数:SEEK_SET/SEEK_CUR/SEEK_END->文件的开始位置/文件的当前位置/文件的末尾
读写流时,当前读写位置都会自动后移

检测流结束和出错
#include<stdio.h>
int ferror(FILE *stream);
int feof(FILE *stream);

ferror()返回1表示流出错;否则返回0
feof()返回1表示文件已到末尾;否则返回0

格式化输出

sprintf/fprintf

#include<stdio.h>
int printf(const char *fmt,...);
//向指定流(文件)输出
int fprintf(FILE *stream,const char *fmt,...);
//以指定的格式把字符串输出到缓冲区s中 
int sprintf(char *s,const char *fmt,...);
  • 成功时返回输出的字符个数;出错时返回EOF
文件I/O
如何理解文件I/O

文件I/O总结
文件I/O总结

文件描述符的含义
  • 每个打开的文件都有一个对应的文件描述符
  • 文件描述符是一个非负整数;Linux系统为每个打开的文件分配一个文件描述符
  • 文件描述符从0开始,依次递增
  • 每个程序打开的文件所对应的文件描述符都是独立的
  • 文件I/O通过文件描述符来完成
  • 由于Linux下的标准I/O是通过对文件I/O做的带缓冲的封装实现的;所以0、1、2对应stdin/stdout/stderr的文件描述符
文件的打开和关闭

一个程序中默认打开的文件是有上限的,一般是1024

open()函数用来打开或者创建一个文件

#include <fcntl.h>
int open(const char *path,int oflag,...); 

成功时返回文件描述符;错误时返回EOF
打开一个已经存在的文件时用两个参数
创建文件时,第三个参数指定新建文件的访问权限
设备文件只能通过open()打开,不能通过open()创建
open()函数参数解析
 open()函数参数解析

close()函数用来关闭一个打开的文件

#include <unisd.h>
int close(int fd); 

成功时返回0;出错时返回EOF
文件关闭时,文件描述符不再代表文件

读取文件

read()函数用于从文件中读取数据

#include <unistd.h>
ssize_t read(int fd,void *buf,size_t count);

第二个参数buf是缓冲区的首地址,用来接收数据;需要先提前申请分配好
第三个参数不能超过缓冲区的大小,否则溢出;一般指定为缓冲区大小
成功时返回实际读取的字节数;出错时返回EOF
读到文件末尾时返回0;可以判断是否读完文件

写入文件

write()函数用于从文件中读取数据

#include <unistd.h>
ssize_t write(int fd,void *buf,size_t count);

成功时返回实际写入的字节数;出错时返回EOF
buf是发送数据的缓冲区
count不应超过buf大小

定位文件

lseek()函数用来定位文件

#include <unistd.h>
off_t lseek(int fd,off_t offset,int whence);

成功时返回当前文件的读写位置;出错时返回EOF
参数offsetwhencefseek()的参数一样

读取目录

opendir()函数用来打开一个目录文件

#include <dirent.h>
DIR *opendir(const char *name);

DIR是用来描述一个打开的目录文件的结构体类型
成功时返回目录流指针;出错时返回NULL

readdir()函数用来读取目录流中的内容

#include <dirent.h>
struct dirent *readdir(DIR *dirp);

struct dirent是用来描述目录流中一个目录项的结构体类型
包含成员 char d_name[256] 参考帮助文档
成功时返回目录流dirp中的下一个目录项
出错或到末尾时返回NULL

closedir()函数用来关闭一个目录文件

#include <dirent.h>
int *closedir(DIR *dirp);

成功时返回0;出错时返回EOF

修改文件访问权限

chmod()/fchmod()函数用来修改文件的访问权限

#include <sys/stat.h>
int chmod(const char *path,mode_t mode);
int fchmod(int fd,mode_t mode);

成功返回0;错误返回EOF
root用户和文件所有者可以修改文件的权限
eg: chmod("text.txt",0666);

获取文件属性

stat()/lstat()/fstat()函数用来获取文件的属性

#include <sys/stat.h>
int stat(const char *path,struct stat *buf);
int lstat(const char *path,struct stat *buf);
int fstat(int fd,struct stat *buf);

获取的属性存储在结构体指针struct stat*
成功返回0;错误返回EOF
如果path时符号链接stat获取的是目标文件的属性;而lstat获取的是链接文件的属性

程序库的概念

  • 库是一个二进制的文件,包含编译好的代码;可被其它程序调用
  • 标准C库、数学库、线程库…
  • 库有源码,可下载后编译,也可以直接安装二进制包
  • 一般系统默认库路径/lib/usr/lib
  • 库是事先编译好的,可以复用的代码
  • 在OS上运行的程序基本上都要使用库,使用库可以提高开发效率
  • Windows和linux下库文件的格式不兼容
  • Linux下包含静态库和共享库
静态库
静态库的特点
  • 链接时把静态库中的相关代码复制到可执行文件中
  • 程序中已包含所需代码,运行时不在需要静态库
  • 程序运行时无需加载库,运行速度快

  • 占用更多的磁盘和内存空间
  • 静态库升级后,程序需要重新编译
静态库的创建
  • 确定库中函数的功能和接口(参数和返回值)
  • 编写库源码 (static.c
  • 编译生成.o目标文件 (gcc -c static.c -Wall
  • 创建静态库,可以多个.oar crs libstatic.a static.o

注意:Linux中静态库的名称是有要求的,一般是以lib + 函数名或功能名 +.a

  • 查看库中的符号信息 (nm libstatic.a
  • 编写应用程序(test.c)测试调用库文件(注意声明)
链接静态库
  • 编译test.c并链接静态库libstatic.agcc -o test test.c -L. -lstatic

-L指定库的搜索路径,本例是当前目录
-l指定要链接库的名称,注意是库名而不是库文件名

共享库
共享库概念
  • 链接时仅记录用到哪个共享库中的哪个符号(即函数),不复制共享库中的代码
  • 程序不包含共享库中的代码,尺寸更小
  • 多个程序可共享同一个共享库
  • 程序运行时需要加载共享库
  • 库升级方便,无需重新编译程序
  • 使用更加广泛
共享库创建
  • 确定库中函数的功能、接口(参数和返回值)
  • 编写库源码 (share.c bye.c
  • 编译生成.o目标文件 (gcc -c -fPIC share.c bye.c -Wall
  • 创建共享库 commongcc -shared -o libcommon.so.1 share.o bye.o
  • 为共享库文件创建链接符号文件 (ln -s libcommon.so.1 libcommon.so

-fPIC告诉编译器生成与位置无关目标文件代码,用到相对寻址;即生成.o代码可以被加载到任意地址执行,而不是固定的地址
1是共享库的版本
符号链接文件命名规则 (lib<库名>.so

  • 编写应用程序(testshare.c)测试调用库文件(添加声明或者单独制作头文件,把声明放在头文件中,本例中制作单独头文件common.h 在测试程序中添加#include "common.h"
链接共享库
  • 编译testshare.c并链接共享库libcommon.sogcc -o testshare testshare.c -L. -lcommon

gcc编译器默认先搜索共享库,如果想直接使用静态库,添加-static参数

加载共享库

如何找到共享库,为了能让系统找到要加载的共享库,有三种方法:

  • 把共享库拷贝到 /lib/usr/lib 目录下,不建议此方法(系统默认的库的搜索路径)
  • 在LD_LIBRARY_PATH环境变量中添加库所在的路径(仅对当前shell有效) eg:添加当前目录到环境变量 export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:.
  • 添加 /etc/ld.so.conf.d/*.conf 文件,执行ldconfig刷新

my.config为例,很简单就是把自己制作的动态库所在路径添加到my.config里;然后执行ldconfig

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值