系统IO中API描述和基本使用
- 一、LInux文件IO的说明
- 1、文件的概念(LInux下一切皆文件)
- 2、Linux系统中文件的分类
- 3、系统IO与标准IO
- 4、如何选择系统IO与标准IO
- 二、系统IO的概念
- 三、系统IO基本API
- 1、文件描述符
- (1) 概念
- (2) 文件描述符的分配规则:
- 2、文件的打开/新建 man 2 open
- 示例代码:
- 3、文件的读取 man 2 read
- 示例代码:
- 4、文件的写入 man 2 write
- 示例代码:
- 5、文件的关闭
- 6、 获取文件的属性信息
- 示例代码:
- 7、修改文件的权限
- 8、判断文件是否存在
- 示例代码:
- 9、设置文件读写的偏移(设置光标)
- 示例代码:
- 10、打印分析出错信息(文末附带错误码集)
- 示例代码:
- 11、字符串的拼接和拆分
- 示例代码:
- 12、 拆分字符串strtok(使用strtok不要使用sscanf)
- 四、系统IO常用的API
- 1、概述
- 2、ioctl()
- 3、dup() 与 dup2()
- 4、fcntl()
- 5、mmap()
- (1) mmap函数使用
- 附件:perror错误码集(133-255为Unknown error)
一、LInux文件IO的说明
1、文件的概念(LInux下一切皆文件)
\quad 在Linux系统语境下,文件(file)一般有两个基本含义:
- 狭义:指普通的文本文件,或二进制文件。包括日常所见的源代码、word文档、压缩包、图片、视频文件等等。
- 广义:除了狭义上的文件外,几乎所有可操作的设备或接口都可视为文件。包括键盘、鼠标、硬盘、串口、触摸屏、显示器等,也包括网络通讯端口(多机通信要用到的文件)、进程间通讯管道(单机同信)等抽象概念。
2、Linux系统中文件的分类
在Linux中,文件总共被分成了7种,他们分别是:
1.普通文件(popular) (符号:-): 存在于外部存储器中,用于存储普通数据。
2.目录文件(directory) (符号:d): 用于存放目录项,是文件系统管理的重要文件类型。
3.管道文件(pipeline) (符号:p): 一种用于进程间通信的特殊文件,也称为命名管道FIFO。
4.套接字文件(socket) (符号:s): 一种用于网络间通信的特殊文件。
5.链接文件(link) (符号:l): 用于间接访问另外一个目标文件,相当于Windows快捷方式。
6.字符设备文件(character) (符号:c): 字符设备在应用层的访问接口 (以字符为单位,跟系统进行数据交换的设备,比如:键盘、鼠标、触摸屏等)
7.块设备文件(block) (符号:b): 块设备在应用层的访问接口 (以块为单位(256字节,1024字节等),跟系统进行数据交换的设备,比如:u盘、内存、硬盘等)
3、系统IO与标准IO
对文件的操作,基本上就是输入输出,因此也一般称为IO接口。
在操作系统的层面上: 这一组专门针对文件的IO接口就被称为系统IO;
在标准库的层面上: 这一组专门针对文件的IO接口就被称为标准IO,如下图所示:
- 系统IO:是众多系统调用当中专用于文件操作的一部分接口。
- 标准IO:是众多标准函数当中专用于文件操作的一部分接口。
从图中还能看到,标准IO实际上是对系统IO的封装,系统IO是更接近底层的接口。
4、如何选择系统IO与标准IO
- 系统IO
- 由操作系统直接提供的函数接口,特点是简洁,功能单一
- 没有提供缓冲区,因此对海量数据的操作效率较低
- 套接字Socket、设备文件的访问只能使用系统IO
- 标准IO
- 由标准C库提供的函数接口,特点是功能丰富
- 有提供缓冲区,因此对海量数据的操作效率高
- 编程开发中尽量选择标准IO,但许多场合只能用系统IO
总的来讲,这两组函数接口在实际编程开发中都经常会用到,都是基本开发技能。
二、系统IO的概念
1、概念:linux系统内核供的接口函数,用来对文件进行读写操作
2、如何查看权限掩码
- 查看掩码值 :umask
> 0002 // 第一个0表示是八进制,后面三个数组才是掩码值
- 修改掩码值 umask 新的掩码值
比如:umask 0022 // 掩码022表示要去掉同组用户的写权限,其他用户的写权限
三、系统IO基本API
1、文件描述符
在linux系统的头文件/usr/src/linux-headers-4.15.0-22/include/linux/sched.h中定义一个结构体
(1) 概念
\quad 函数 open() 的返回值,是一个整型 int 数据。这个整型数据,实际上是内核中的一个称为 fd_array 的数组的下标:
打开文件时,内核产生一个指向 file{} 的指针,并将该指针放入一个位于 file_struct{} 中的数组 fd_array[] 中,而该指针所在数组的下标,就被 open() 返回给用户,用户把这个数组下标称为文件描述符,如上图所示。
struct task_struct //用来保存当前程序运行时候产生的状态信息
{当前程序打开的所有文件的属性信息struct files_struct *files --->结构体数组的起始位置(该结构体数组每个成员分别存储打开的每一个文件的属性信息)文件描述就是该文件在数组中的下标位置struct files_struct 数组名[元素个数]struct files_struct *files=数组名 当前程序占用的内存空间大小当前程序栈的使用情况当前程序堆的使用情况......
}
- 文件描述符从0开始,每打开一个文件,就产生一个新的文件描述符。
- 可以重复打开同一个文件,每次打开文件都会使内核产生系列结构体,并得到不同的文件描述符
- 由于系统在每个进程开始运行时,都默认打开了一次键盘、两次屏幕,因此0、1、2描述符分别代表标准输入(键盘)、标准输出(键盘)和标准出错(键盘)三个文件(两个硬件)。
(2) 文件描述符的分配规则:
- 第一个:0 1 2已经默认被键盘,液晶屏占用了,只能从3开始分配
- 第二个:linux系统总是把目前没有被占用的最小的文件描述符分配给你
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>int main(int argc, char const *argv[])
{int fd1, fd2, fd3;// 可读可写的权限打开1.txtfd1 = open("./1.txt", O_RDWR);if (fd1 == -1){printf("open fd1 error!\n");return -1;}printf("fd1的文件描述符是%d\n",fd1);// 可读可写的权限打开2.txtfd2 = open("./1.txt", O_RDWR);if (fd2 == -1){printf("open fd2 error!\n");return -1;}printf("fd2的文件描述符是%d\n",fd2);// 关闭fd1文件close(fd1);// 关闭fd2文件close(fd2);// 可读可写的权限打开3.txtfd3 = open("./3.txt", O_RDWR);if (fd1 == -1){printf("open fd3 error!\n");return -1;}printf("fd3的文件描述符是%d\n",fd3);// 关闭fd3文件close(fd3);return 0;
}
/*
执行结果:fd1的文件描述符是3fd2的文件描述符是4fd3的文件描述符是3
*/
- 第三个:文件描述符最大值是多少–》0—1023
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>int main(int argc, char const *argv[])
{
/* 文件描述符最大值是多少--》0---1023 */int fd;for (int i = 0; i < 2000; i++){fd = open("./1.txt", O_RDWR);if (fd == -1){printf("open fd error!\n");return -1;}printf("文件描述符是%d\n",fd);}return 0;
}
2、文件的打开/新建 man 2 open
注:linux中所有的函数,只要参数需要用到多个宏定义,都是使用按位或连接起来
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
// 第一个版本的open
int open(const char *pathname, int flags);返回值:成功 --》返回文件描述符失败 --》返回-1参数:pathname --》你要打开的那个文件的路径名flags --》O_RDONLY 只读O_WRONLY 只写O_RDWR 可读写情况一: 你想使用的权限跟文件本身的权限不一致,会打开失败(普通用户的身份运行程序,超级用户不受权限的约束)你想O_RDWR但是文件本身是只读/只写 --》会导致失败O_APPEND 以追加的方式打开文件,打开文件的时候光标会自动到文件末尾O_CREAT 新建文件O_EXCL 跟O_CREAT配合使用,表示如果文件存在就失败退出,不新建,不存在就新建O_TRUNC 跟O_CREAT配合使用,表示如果文件存在就清空覆盖掉原来的文件,不存在就新建
// 第二个版本的open
int open(const char *pathname, int flags, mode_t mode);参数:mode --》你创建一个文件的时候,顺便给这个文件设置一个权限使用的时候直接写个8进制的数来表示权限写成0777,不要写成777(编译没有错误,修改结果不正确)注:设置权限会受到umask掩码值的影响(之前的文件先手动删除,再使用O_TRUNC覆盖,但是权限改不了)计算公式:你写的权限&(~umask) = 新建的文件最终的权限值修改umask的值: umask 新的值
示例代码:
// 第一个版本的open
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>int main()
{int fd;//新建一个记事本//O_EXCL表示文件存在就新建失败,不存在就自动创建//fd=open("./new.txt",O_CREAT|O_EXCL|O_RDWR); //O_TRUNC表示文件存在就把源文件清空覆盖,不存在就自动创建fd=open("./new.txt",O_CREAT|O_TRUNC|O_RDWR);if(fd==-1){printf("新建文件失败了!\n");return -1;}return 0;
}
// 第二个版本的open
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>int main()
{int fd;//新建一个记事本//O_EXCL表示文件存在就新建失败,不存在就自动帮你创建//fd=open("/home/gec/new.txt",O_CREAT|O_EXCL|O_RDWR,0777); //O_TRUNC表示文件存在就把源文件清空覆盖,不存在就自动帮你创建fd=open("/home/gec/new.txt",O_CREAT|O_TRUNC|O_RDWR,0777);if(fd==-1){printf("新建文件失败了!\n");return -1;}return 0;
}
3、文件的读取 man 2 read
注: ssize_t和size_t都是用typedef给长整型取的别名
#include <unistd.h>
ssize_t read(int fd, void *buf, size_t count); 返回值(重点):成功 --》返回成功读取到的字节数如果一个文件读取完毕,再次调用read读取,read返回0个字节失败 --》-1参数:fd --》要读取的那个文件的文件描述符buf --》存放读取到的内容count --》读取多少字节的数据如果count的值超过了文件中实际的字节数,read按照实际大小读取char buf[100]read(fd, buf, 451551545字节byte); //导致数组溢出(段错误的风险)fd代表视频(500兆)
示例代码:
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdio.h>int main(int argc, char const *argv[])
{int fd;char buf[32] = {0}; // 存放读取到的内容// 以可读可写的权限打开1.txt,open打开默认光标在起始位置fd = open("./1.txt", O_RDWR);if (fd == -1){printf("打开fd文件失败\n");return -1;}printf("fd的文件描述符是:%d\n", fd); // fd的文件描述符是:3// 从光标起始位置开始读取1.txt文件的内容// ssize_t ret = read(fd, buf, 5); // 读取到的文件内容是:12345, 读取到的字节数是:5// ssize_t ret = read(fd, buf, sizeof(buf)); // 读取到的文件内容是:1234567890, 读取到的字节数是:10ssize_t ret = read(fd, buf, 12); // 读取到的文件内容是:1234567890, 读取到的字节数是:10printf("读取到的文件内容是:%s, 读取到的字节数是:%ld\n", buf, ret); // 再次读取ret = read(fd, buf, 12); // 读取到的文件内容是:1234567890, 读取到的字节数是:0printf("读取到的文件内容是:%s, 读取到的字节数是:%ld\n", buf, ret); // 关闭文件close(fd);return 0;
}
4、文件的写入 man 2 write
ssize_t write(int fd, const void *buf, size_t count);返回值:成功:count写多少,返回值就是多少失败:-1参数:fd --》要写入的那个文件的文件描述符buf --》存放要写入的内容count --》打算写入多少字节的数据 write(fd,"hello",5) //返回值是5write(fd,"hello",10) //返回值是10,此时write发现hello不够10个字节,会找垃圾数凑够10个字节写入到文件中write(fd,"hello",100) //返回值是100,此时write发现hello不够100个字节,会找垃圾数凑够100个字节写入到文件中注:count的值不能乱写
示例代码:
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdio.h>
#include <string.h>/*write和read混合使用需要注意的问题读/写操作都会自动改变光标位置读/写永远都是从当前光标后面开始
*/int main(int argc, char const *argv[])
{int fd;char *buf = "hello"; int a = 12345;float b = 99.9;fd = open("./2.txt", O_RDWR);if (fd == -1){printf("打开fd文件失败\n");return -1;}// 写入字符串write(fd, buf, strlen(buf));// 写入整数write(fd, &a, sizeof(a));// 写入浮点数write(fd, &b, sizeof(b));// 将文件指针重置到文件开头lseek(fd, 0, SEEK_SET);char buf1[10] = {0}; int a1;float b1;// 读取字符串read(fd, buf1, strlen(buf));// 确保字符串以 '\0' 结尾buf1[strlen(buf)] = '\0';// 读取整数read(fd, &a1, sizeof(a1));// 读取浮点数read(fd, &b1, sizeof(b1));printf("buf1:%s\n", buf1); // buf1:helloprintf("a1:%d\n", a1); // a1:12345printf("b1:%f\n", b1); // b1:99.900002close(fd);return 0;
}
5、文件的关闭
int close(int fd);返回值:成功 0 失败 -1参数:fd --》你要关闭的文件描述符
6、 获取文件的属性信息
获取大小,权限,类型…
int stat(const char *pathname, struct stat *buf);返回值:成功 0 失败 -1参数:pathname --》文件路径名buf --》结构体指针,用来存放获取到文件属性信息 struct stat{mode_t st_mode; //保存文件的类型,权限off_t st_size; //保存文件大小}用途一: 获取文件大小st_size 里面就是大小用途二:获取权限 (所代表的是八进制数)S_IRWXU //宏定义 当前用户可读,可写,可执行S_IRUSR //宏定义 当前用户可读S_IWUSR //宏定义 当前用户可写S_IXUSR //宏定义 当前用户可执行S_IRWXG //宏定义 同组用户可读,可写,可执行S_IRGRP //宏定义 同组用户可读S_IWGRP //宏定义 同组用户可写S_IXGRP //宏定义 同组用户可执行S_IRWXO //宏定义 其他用户可读,可写,可执行S_IROTH //宏定义 其它用户可读S_IWOTH //宏定义 其它用户可写S_IXOTH //宏定义 其它用户可执行struct stat mystat;if(mystat.st_mode&S_IRUSR)printf("这个文件对于当前用户是可读的!\n");用途三:判断文件类型 (所代表的是八进制数)S_IFMT 0170000S_IFSOCK 0140000 套接字S_IFLNK 0120000 软链接S_IFREG 0100000 普通文件S_IFBLK 0060000 块设备S_IFDIR 0040000 目录S_IFCHR 0020000 字符设备S_IFIFO 0010000 管道写法一:与运算判断switch(mystat.st_mode&S_IFMT) // {case S_IFREG: //普通文件break;case S_IFDIR: //目录文件}注意:如果写成if条件判断,表达式要括起来防止优先级出问题if((mystat.st_mode&S_IFMT)==S_IFREG)写法二:宏函数(带参数的宏定义)判断S_ISDIR(m) 目录S_ISREG(m) 普通文件S_ISCHR(m) 字符设备S_ISBLK(m) 块设备S_ISFIFO(m) 管道S_ISLNK(m) 软链接S_ISSOCK(m) 套接字if(S_ISREG(mystat.st_mode))printf("这是个普通文件!\n");
示例代码:
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdio.h>
#include <string.h>int main(int argc, char const *argv[])
{struct stat mystat;// 判断参数个数if (argc != 2){printf("需输入参数!\n");return -1;}// 获取文件大小int ret = stat(argv[1], &mystat);if (ret == -1){printf("获取文件属性失败\n");return -1;}printf("当前文件的大小:%ld\n", mystat.st_size);// 获取文件的权限if (mystat.st_mode & S_IRUSR){printf("当前文件权限是可读的\n");}if (mystat.st_mode & S_IWUSR){printf("当前文件权限是可写的\n");}if (mystat.st_mode & S_IXUSR){printf("当前文件权限是可执行的\n");}// 获取文件类型-写法1// switch (mystat.st_mode & S_IFMT)// {// case S_IFLNK:// printf("当前文件类型是软链接\n");// break;// case S_IFREG:// printf("当前文件类型是普通文件\n");// break;// default:// break;// }// 获取文件类型-写法2if(S_ISREG(mystat.st_mode))printf("普通文件!\n");printf("1.txt文件权限:%o\n", mystat.st_mode); // 1.txt文件权限:100777return 0;
}
/*
执行结果:当前文件的大小:10当前文件权限是可读的当前文件权限是可写的当前文件权限是可执行的普通文件!1.txt文件权限:100777
*/
7、修改文件的权限
int chmod(const char *pathname, mode_t mode);返回值:成功 0 失败 -1参数:pathname --》文件路径名mode --》八进制数,表示权限0777注:chmod命令修改权限以及chmod函数修改权限,都不需要考虑掩码umask不要在共享文件夹中修改权限,不准确open(新建一个文件,设置权限) 需要考虑计算掩码
8、判断文件是否存在
int access(const char *pathname, int mode);返回值:文件存在 0 不存在 -1参数:pathname --》文件路径名mode --》R_OK //判断文件是否可读W_OK //判断文件是否可写X_OK //判断文件是否可执行F_OK //判断文件是否存在
示例代码:
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdio.h>
#include <string.h>int main(int argc, char const *argv[])
{int ret = 0;// 判断参数个数if (argc < 2){printf("需输入参数!\n");return -1;}if (access(argv[1], F_OK) == 0){printf("%s文件存在\n", argv[1]);chmod(argv[1], 777);}else{printf("%s文件不存在\n", argv[1]);return -1;}return 0;
}
9、设置文件读写的偏移(设置光标)
作用:程序员可以依据自己的实际需要,往文件的任何位置读取/写入内容
off_t lseek(int fd, off_t offset, int whence);返回值:成功 返回当前偏移位置距离文件开头的字节数失败 -1参数:fd --》要设置读写偏移的文件的文件描述符offse --》要设置的读写偏移量,字节whence --》从文件的哪个位置开始偏移SEEK_SET //起始位置SEEK_CUR //当前位置,动态变化SEEK_END //末尾位置比如: lseek(fd,10,SEEK_SET); //把读写偏移设置成从起始位置往后偏移10个字节
用法一:lseek去设置读写偏移量
用法二:lseek求文件的大小lseek(fd,0,SEEK_END); // 利用返回值求文件大小
总结:
第一:画图分析最开始open打开(不能使用O_APPEND),光标从第一个字符前面开始画 第二:read还是write光标都会变动第三:lseek从哪里开始移动光标(看第三个参数和第二个参数) 第四:read/write都从光标后面一个位置开始
示例代码:
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdio.h>
#include <string.h>int main(int argc, char const *argv[])
{char buf[12] = {0};int fd = open("./1.txt", O_RDWR); // 1234567890if (fd == -1){printf("打开fd文件失败\n");return -1;}// 从起始位置往后偏移5位lseek(fd, 5, SEEK_SET); // 读取文件// read(fd, buf, 5); // 读取到的内容是:67890// printf("读取到的内容是:%s\n", buf); // 光标偏移后写入write(fd, "nihao", 5);lseek(fd, -2, SEEK_CUR); read(fd, buf, 5); // 读取到的内容是:ao123printf("读取到的内容是:%s\n", buf); lseek(fd, 17, SEEK_SET); // 光标偏移后写入write(fd, "good", 4);// 可通过lseek的返回值计算文件的大小int size = lseek(fd, 0, SEEK_END); printf("1.txt文件的大小是:%d\n", size); // 1.txt文件的大小是:21close(fd);return 0;
}
10、打印分析出错信息(文末附带错误码集)
#include <errno.h>
void perror(const char *s);参数:s --》打印信息
底层原理:linux系统把所有常见的错误类型用宏定义定义成一个个错误码文件不存在 1权限不够 2内存溢出 3#define ENOENT 2 /* No such file or directory */#define ESRCH 3 /* No such process */#define EINTR 4 /* Interrupted system call */....linux的errno.h中有定义全局变量 int errno用来记录保存当前的错误码perror源码中依据errno中的错误码找到错误的具体原因
示例代码:
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdio.h>
#include <string.h>
#include <errno.h>int main(int argc, char const *argv[])
{int fd = open("a.txt", O_RDWR);if (fd == -1){// printf("打开fd文件失败\n");perror("打开fd文件失败\n");printf("errno的值是:%d\n", errno); // return -1;}close(fd);return 0;
}/*
替换成perror打印错误信息 此时输出打开fd文件失败: No such file or directoryerrno的值是:2
*/
11、字符串的拼接和拆分
int sprintf(char *str, const char *format, ...); //变参函数(参数个数,类型不确定)参数:str --》存放拼接后的结果format --》打算按照什么格式去拼接数据int sscanf(const char *str, const char *format, ...);参数:str --》你要拆分的字符串format --》打算按照什么格式去拆分字符
示例代码:
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdio.h>
#include <string.h>
#include <errno.h>int main(int argc, char const *argv[])
{int year = 2025;char str[20] = "nihao";float dec = 99.9;// 定义数据存放拼接后的字符串char buf[100] = {0};// 拼接字符串 拼接好后的字符串 buf: nihao-2025-99.900002sprintf(buf, " %s-%d-%f", str, year, dec); // printf("拼接后的字符串 buf:%s\n", buf);// 拆分字符串char date[128] = "2025-03-16-12.00";int y;int m;int d;float h;// 拆分后:2025#3#16#12.000000sscanf(date, "%d-%d-%d-%f", &y, &m, &d, &h);printf("拆分后:%d#%d#%d#%f\n",y, m, d, h);// 使用sscanf时,无法实现的方式char otherbuf[100] = "nihao@hello@world@666";char a1[10] = {0};char b1[10] = {0};char c1[10] = {0};char d1[10] = {0};/* 当需要拆分的数据全都是字符串时,sscanf会把%s当成一个整体,不会按照预定格式进行输出*/sscanf(otherbuf, "%s@%s@%s@%s", a1, b1, c1, d1);// 此时输出 :nihao@hello@world@666o@world@6666// printf("%s%s%s%s\n", a1, b1, c1, d1); printf("%s\n", a1); printf("%s\n", b1); printf("%s\n", c1); printf("%s\n", d1); /*输出:nihao@hello@world@666o@world@6666*/ return 0;
}
12、 拆分字符串strtok(使用strtok不要使用sscanf)
char *strtok(char *str, const char *delim); 返回值:切割得到的字符串切割完毕,切割失败 返回NULL参数:str --》你要切割的字符串delim --》切割的标准(你打算用哪些字符作为字符串切割的分隔字符)
总结:拼接字符串sprintf切割拆分字符串strtok
四、系统IO常用的API
1、概述
\quad 对文件的操作,除了最基本的打开、关闭、读、写、定位之外,还有很多特殊的情况,比如用于沟通应用层与底层驱动之间的ioctl、万能工具箱fcntl、内存映射mmap等等,熟练使用这些API,是日常开发的必备技能。
2、ioctl()
\quad 该函数是沟通应用层和驱动层的有力武器,底层开发人员在为硬件设备编写驱动的时候,常常将某些操作封装为一个函数,并为这些接口提供一个所谓的命令字,应用层开发者可以通过 ioctl() 函数配合命令字,非常迅捷地绕过操作系统中间层层机构直达驱动层,调用对应的功能。
\quad 从这个意义上讲,函数 ioctl() 像是一个通道,只提供函数调用路径,具体的功能由所谓命令字决定,下面是函数的接口规范说明:
- 关键点:
- request 就是所谓的命令字。
- 底层驱动开发者可以自定义命令字。
- 对于某些常见的硬件设备的常见功能,系统提供了规范的命令字。
- 示例代码:
int main(void)
{// 打开一盏LED灯int led = open("/dev/Led", O_RDWR);// 通过命令字 LEDOP 及其携带的0/1参数,控制LED灯的亮灭// 此处,LEDOP 是底层开发者自定义的命令字ioctl(led, LEDOP, 1);ioctl(led, LEDOP, 0);// 打开一个摄像头int cam = open("/dev/video0", O_RDWR);// 通过命令字 VIDIC_STREAMON 及其携带参数 vtype 启动摄像头enum v4l2_buf_type vtype= V4L2_BUF_TYPE_VIDEO_CAPTURE;ioctl(cam, VIDIOC_STREAMON, &vtype);
}
3、dup() 与 dup2()
- dup 是英语单词 duplicate 的缩写,意即“复制”。
- 这两个函数功能类似,都是用来“复制”文件描述符,接口规范如下:
\quad dup()会将指定的旧文件描述符 oldfd 复制一份,并返回一个系统当前未用的最小的新文件描述符。注意,此时这新旧两个文件描述符是可以互换的,因为它们本质上指涉的是同一个文件,因此它们共享文件的读写偏移量和文件的状态标签,比如使用lseek()对新文件描述符修改文件偏移量,这个操作会同时影响旧文件描述符oldfd,再如,使用read()对新文件描述符读取文件部分内容后,可以继续对旧文件描述符读取后续内容。
\quad dup2()跟dup()几乎完全一样,不同的地方在于前者可以指定新文件描述符的具体数值,而不局限于系统当前未用描述符的最小值。这样一来,就可以通过dup2()指定一个已用的描述符,来达到重定向文件流的作用。
示例代码:
int main()
{// 打开文件 a.txt ,获得其文件描述符 fd1// 此处 fd1 就代表了这个文件及其配套的系统资源int fd1 = open("a.txt", O_RDWR);// 复制文件描述符 fd1,默认得到最小未用的文件描述符dup(fd1);// 复制文件描述符 fd1,并指派为 100dup2(fd1, 100);
}
- 解析:
\quad 使用dup函数时,会自动分配当前未用的最小的文件描述符,如上述代码,由于进程默认打开了0、1、2作为标准输入输出,于是 fd1 就是3,新产生的文件描述符就是4,而 dup2 函数可以任意指定文件描述符的数值,如果指定的文件描述符已经有所指代,那么原先指代关系将会被替换。这种情况被称为“重定向”。
4、fcntl()
\quad 该函数的名字是 file control 的缩写,顾名思义,它可以用来“控制”文件,与 ioctl 类似,此处的 “控制” 含义广泛,具体内容由其第二个参数命令字来决定,fcntl 的接口规范如下:
- 关键点:
- fcntl 是个变参函数,前两个参数是固定的,后续的参数个数和类型取决于 cmd 的具体数值。
- 第二个参数 cmd,称为命令字。
- 命令字有很多,常用的如下:
- 示例代码①:
// 获取指定文件fd的标签属性
int flag = fcntl(fd, F_GETFL);// 在其原有属性上,增添非阻塞属性
flag |= O_NONBLOCK;
fcntl(fd, F_SETFL, flag);
- 示例代码②:
// 将套接字sockfd的信号属主设置为本进程
fcntl(sockfd, F_SETOWN, getpid());
\quad 在网络编程中,当一个套接字处于异步通信状态并收到一个远端的数据时,就会使得内核产生一个信号SIGIO,此时我们可以通过上述fcntl()技巧告诉内核这个信号的接收者。一般而言,接收者收到该信号后,就知道套接字上有数据等待处理了。
5、mmap()
(1) mmap函数使用
\quad 该函数全称是 memory map,意为内存映射,即将某个文件与某块内存关联起来,达到通过操作这块内存来间接操作其所对应的文件的效果。
- 关键点:
- mmap函数的flags参数是有很多的,上表只罗列了最简单的几个,详细信息请使用 man 手册进行查阅。
- mmap函数理论上可以对任意文件进行内存映射,但通常用来映射一些比较特别的设备文件,比如液晶屏LCD。
注意:
\quad 在较旧的Linux内核(如2.6内核)中,可以直接使用mmap()来给LCD设备映射内存,但在较新Linux内核(如4.4内核)中,则需要经由DRM统一管理,不可直接mmap映射显存。
int main()
{// 以只读方式打开一个文件int fd = open("a.txt", O_RDWR);// 申请一块大小为1024字节的映射内存,并将之与文件fd相关联char *p = mmap(NULL, 1024, PROT_READ|PROT_WRITE,MAP_SHARED, fd, 0);// 将该映射内存的内容打印出来(即其相关联文件fd的内容)printf("%s\n", p);// 通过操作内存,间接修改了文件内容p[0] = 'x';printf("%s\n", p);// 解除映射munmap(p, 1024);return 0;
}
注意几点:
1.使用 mmap 映射内存时,指定的读写权限必须是打开模式的子集。
2.映射模式为 MAP_SHARED 时,修改的内容会影响文件。
3.映射模式为 MAP_PRIVATE 时,修改的内容不会影响文件。