今天正式进入应用层的学习,今天主要学习文件IO和系统调用的相关库的使用,这部分内容是第一部分的延展。那么,让我们开始吧。
🧑🏻作者简介:一个学嵌入式的年轻人
✨联系方式:2201891280(QQ)
📔源码地址:https://gitee.com/xingleigao/study_qianrushi
⏳全文大约阅读时间: 60min
文章目录
文件IO——介绍
posix(可移植操作系统接口)定义的一组函数
不提供缓冲机制,每次读写操作都引起系统调用
核心概念是文件描述符
Linux下, 标准IO基于文件IO实现
标准io 文件io(低级IO) 打开 fclose,freopen,fdopen open 关闭 fclose close 读 getc,fgetc,getchar,fgets,gets,fread read 写 putc,fputc,putchar,fputs,puts,fwrite write
文件描述符
每个打开的文件都对应一个文件描述符。
文件描述符是一个非负整数。Linux为程序中每个打开的文件分配一个文件描述符。
文件描述符从0开始分配,依次递增。
文件IO操作通过文件描述符来完成。
上节课有说过三个默认的文件打开表象,分别就是012对应stdin stdout stderrr
文件操作
open函数
#include <fcntl.h> int open(const char *path, int oflag, mode_t mode);
成功时返回文件描述符;出错时返回EOF
打开文件时使用两个参数
创建文件时第三个参数指定新文件的权限
只能打开设备文件
参数 作用 pathname 打开文件名 flags O_RDONLY:只读方式打开
O_WRONLY:可写方式打开文件
O_RDWD:读写方式打开(前三个参数互斥)
O_CREAT:如果该文件不存在,就创建一个新文件,并用第三个参数为其设置权限
O_NOCTTY:如果文件为终端, 终端不可以作为调用open()系统调用的那个进程的控制终端
O_TRUNC:如果文件已经存在,那么打开文件时先删除文件中原有数据
O_APPEND:以添加方式打开文件,对文件的写操作会在文件末尾进行mode 被打开的文件存取权限,八进制,比如777
close函数
#include <unistd.h> int close(int fd);
- 成功时返回0;出错时返回EOF
- 程序结束时自动关闭所有打开的文件
- 文件关闭后,文件描述符不再代表文件
read函数
#include <unistd.h> ssize_t read(int fd, void *buf, size_t count);
- 成功时返回实际读取的字节数;出错时返回EOF
- 读到文件末尾时返回0
- buf是接收数据的缓冲区
- count不应超过buf大小
综合应用代码
#include <unistd.h> #include <fcntl.h> #include <stdio.h> int main(){ int fd; int n = 0; char buf[2]; fd = open("1.txt", O_RDONLY|O_EXCL, 0666); if(fd){ while(read(fd, buf, 1) > 0){ n++; } printf("n=%d\n", n); } close(fd); return 0; }
write函数
#include <unistd.h> ssize_t write(int fd, void *buf, size_t count);
- 成功时返回实际写入的字节数;出错时返回EOF
- buf是发送数据的缓冲区
- count不应超过buf大小
示例代码
#include <unistd.h> #include <fcntl.h> #include <stdio.h> int main(){ int fd; int n = 0; char buf[11]; fd = open("1.txt", O_RDWR|O_APPEND, 0666); if(fd){ fgets(buf, 10, stdin); write(fd, buf, 10); } close(fd); return 0; }
lseek函数
#include <unistd.h> off_t lseek(int fd, off_t offset, intt whence);
- 成功时返回当前的文件读写位置;出错时返回EOF
- 参数offset和参数whence同fseek完全一样
目录操作
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
综合示例代码#include <dirent.h> #include <stdio.h> int main(){ DIR *dir; struct dirent *dent; dir = opendir("."); if(dir) while((dent = readdir(dir)) != NULL) printf("%s\n", dent->d_name); return 0; }
chmod/fchmod函数用来修改文件的访问权限:
#include <sys/stat.h> int chmod(const char *path, mode_t mode); int fchmod(int fd, mode_t mode);
- 成功时返回0;出错时返回EOF
- root和文件所有者能修改文件的访问权限
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);
- 成功时返回0;出错时返回EOF
- 如果path是符号链接
stat
获取的是目标文件的属性;而lstat
获取的是链接文件的属性struct stat是存放文件属性的结构体类型:
struct stat { dev_t st_dev; //文件的设备编号 ino_t st_ino; //节点 mode_t st_mode; //文件的类型和存取的权限 nlink_t st_nlink; //连到该文件的硬连接数目,刚建立的文件值为1 uid_t st_uid; //用户ID gid_t st_gid; //组ID dev_t st_rdev; //(设备类型)若此文件为设备文件,则为其设备编号 off_t st_size; //文件字节数(文件大小) unsigned long st_blksize; //块大小(文件系统的I/O 缓冲区大小) unsigned long st_blocks; //块数 time_t st_atime; //最后一次访问时间 time_t st_mtime; //最后一次修改时间 time_t st_ctime; //最后一次改变时间(指属性) };
通过系统提供的宏来判断文件类型:
st_mode & 0170000
- S_ISREG(st_mode) 0100000
- S_ISDIR(st_mode) 0040000
- S_ISCHR(st_mode) 0020000
- S_ISBLK(st_mode) 0060000
- S_ISFIFO(st_mode) 0010000
- S_ISLNK(st_mode) 0120000
- S_ISSOCK(st_mode) 0140000
通过系统提供的宏来获取文件访问权限:
- S_IRUSR 00400 bit:8
- S_IWUSR 00200 7
- S_IXUSR 00100 6
- S_IRGRP 00040 5
- S_IWGRP 00020 4
- S_IXGRP 00010 3
- S_IROTH 00004 2
- S_IWOTH 00002 1
- S_IXOTH 00001 0
综合示例
#include <dirent.h>
#include <stdio.h>
#include <sys/stat.h>
int main(){
DIR *dir;
struct dirent *dent;
struct stat buf;
dir = opendir(".");
if(dir){
while((dent = readdir(dir)) != NULL){
stat(dent->d_name, &buf);
printf("%s %d\n", dent->d_name, (int)buf.st_size);
}
}
return 0;
}
程序库
库的基本概念
库是一个二进制文件,包含的代码可被程序调用,事先编译好的,可以复用的代码。
标准C库、数学库、线程库……
库有源码,可下载后编译;也可以直接安装二进制包/lib
、/usr/lib
在OS上运行的程序基本上都要使用库。使用库可以提高开发效率。
Windows和Linux下库文件的格式不兼容
Linux下包含静态库和共享库
静态库特点
编译(链接)时把静态库中相关代码```复制到``可执行文件中
特点:
- 程序中已包含代码,运行时不再需要静态库
- 程序运行时无需加载库,运行速度更快
- 占用更多磁盘和内存空间
- 静态库升级后,程序需要重新编译链接
静态库创建
- 确定库中函数的功能、接口
- 编写库源码hello.c
#include <stdio.h>
void hello(void) {
printf(“hello world\n”);
return;
}- 编译生成目标文件
$gcc -c hello.c -Wall
- 创建静态库 hello
$ar crs libhello.a hello.o
- 查看库中符号信息
$nm libhello.a
hello.o:
0000000 T hello
U puts- 编写应用程序test.c
#include <stdio.h>
void hello(void);
int main() {
hello();
return 0;
}- 编译test.c 并链接静态库libhello.a
$ gcc -o test test.c -L. -lhello
$ ./test
hello world
共享库
- 编译(链接)时仅记录用到哪个共享库中的哪个符号,不复制共享库中相关代码
- 程序不包含库中代码,尺寸小
- 多个程序可共享同一个库
- 程序运行时需要加载库
- 库升级方便,无需重新编译程序
- 使用更加广泛
共享库创建
- 确定库中函数的功能、接口
- 编写库源码hello.c
#include <stdio.h>
void hello(void) {
printf(“hello world\n”);
return;
}- 编译生成目标文件
$gcc -c -fPIC hello.c -Wall
- 创建共享库 common
$ gcc -shared -o libcommon.so.1 hello.o- 为共享库文件创建链接文件
$ ln -s libcommon.so.1 libcommon.so- 符号链接文件命名规则
lib<库名>.so- 编写应用程序test.c
#include <stdio.h>
#include “common.h”
int main() {
hello();
return 0;
}
其中common.h为
void hello();- 编译test.c 并链接共享库libcommon.so
$ gcc -o test test.c -L. -lcommon
共享库使用
执行程序
./test ./test: error while loading shared libraries: libcommon.so cannot open shared object file : No such file or directory
添加共享库的加载路径(环境变量)
export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:. ./test hello world bye!
- 把库拷贝到/usr/lib和/lib目录下(极度不推荐)
- 在
LD_LIBRARY_PATH
环境变量中添加库所在路径- 添加
/etc/ld.so.conf.d/*.conf
文件,执行ldconfig刷新
写在最后
文件IO今日完结,我尽量一天一更,大家和我一起变强呀!明天开始进入多线程编程的一章!最后三连即可提高学习效率!!!
另外我在更新的就是算法笔记的一些例题笔记,这个系列是用于提高我的算法能力,如果有兴趣对算法领域感兴趣找不到合适的入门文章也可以追更,如果我更新的太慢了请大家点赞收藏,一键三连才能更有更新的动力呀0.0