一、文件数据输出/输入。 1、如何读取文件的数据? -> read() -> man 2 read 函数功能:read from a file descriptor //读取一个文件描述符的数据 头文件: #include <unistd.h>
原型: ssize_t read(int fd, void *buf, size_t count);
参数: fd:文件描述符 buf:数据缓冲区 count:尝试读取的字节数(愿望值)
返回值: 成功:已经成功读取到的字节数 失败:-1
例子1: 尝试从文本中读取一些数据出来。
注意:从文本文档中读取出来的数据,都是字符串。
int main(int argc,char *argv[]) { //1. 打开目标文件 int fd; fd = open("./test.txt",O_RDONLY); if(fd < 0) { printf("open file error!\n"); } //2. 读取数据 char buf[100] = {0}; int n; n = read(fd,buf,5); printf("n = %d\n",n); printf("from file:%s\n",buf); //3. 关闭文件 close(fd); return 0; } 结果: n = 10 from file:helloworld
练习1: 重复读取一个文件,那么第二次读取的时候是在第一次基础上继续读,还是重新读? -> 继续读
int main(int argc,char *argv[]) { //1. 打开目标文件 int fd; fd = open("./test.txt",O_RDONLY); if(fd < 0) { printf("open file error!\n"); } //2. 读取数据 char buf[100] = {0}; int n; n = read(fd,buf,5); printf("n = %d\n",n); printf("from file:%s\n",buf); //hello n = read(fd,buf,5); printf("n = %d\n",n); printf("from file:%s\n",buf); //world //3. 关闭文件 close(fd); return 0; }
2、写入数据到文件中。 -> write() -> man 2 write 函数功能: write to a file descriptor //写入数据到文件描述符中 头文件: #include <unistd.h>
原型: ssize_t write(int fd, const void *buf, size_t count); 参数: fd:文件描述符 buf:需要写入到文件中的内容 count:写入的字节数
返回值: 成功:真正写入字节数 失败:-1
例子2:尝试写一些数据到空白的文件中。
#include <stdio.h> #include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> #include <unistd.h> #include <string.h>
int main(int argc,char *argv[]) { int fd; fd = open("./test2.txt",O_WRONLY|O_TRUNC); if(fd < 0) { printf("open file error!\n"); } char buf[20] = "helloworld"; int n; //n = write(fd,buf,strlen(buf)); //内容是:helloworld //printf("n = %d\n",n); //10 //n = write(fd,buf,5); //内容是:hello //printf("n = %d\n",n); //5 //n = write(fd,buf,20); //内容是:helloworld + 10个空格 //printf("n = %d\n",n); //20 n = write(fd,buf,100); //内容是:helloworld + 10个空格 + 乱码 printf("n = %d\n",n); //n=100 close(fd); return 0; } 练习2: 重复写入一个文件,那么第二次写入的时候是在第一次基础上继续写,还是重新写? -> 继续写
n = write(fd,buf,5); //内容是:hello printf("n = %d\n",n); //5 n = write(fd,buf,5); //内容是:hello printf("n = %d\n",n); //5
文件内容:hellohello
练习3: 先读取一些数据,然后再写,那么写的时候是重新写,还是读取完的那个位置继续写? -> 继续写
//文本内容:yueqianhelloworld
read(fd,buf,5); //yueqi printf("buf = %s\n",buf); //yueqi char *p = "apple"; write(fd,p,strlen(p)); //内容:yueqiappleloworld
练习4: 验证O_APPEND在写操作时,会追加? -> 会追加。
打开一个文件,写入数据 -> 从头开始写的。 打开一个文件|O_APPEND,写入数据 -> 从文件末尾开始写。
fd = open("./test2.txt",O_RDWR|O_APPEND); // yueqiappleloworld char *p = "kkk"; write(fd,p,strlen(p));
文本内容:yueqiappleloworldkkk
O_APPEND会影响读操作吗? -> 不会
打开一个文件,读取数据 -> 从头开始读 打开一个文件|O_APPEND,读取数据 -> 从头开始读 二、文件偏移量。 1、什么是文件偏移量? 文件偏移量就是文件当前的定位,默认打开一个文件时,文件的定位都是在最开头。
2、怎么样才能使得文件偏移量发生偏移? 1)使用读写操作的函数可以使得文件偏移量发生偏移。 fd=open("test.txt"); //偏移量:0 write(fd,"hello",5); //偏移量:5
2)如何使得不调用读写函数前提下发生偏移? -> lseek() -> man 2 lseek 函数功能:reposition read/write file offset //重新定位读写的偏移量
头文件: #include <sys/types.h> #include <unistd.h>
原型: off_t lseek(int fd, off_t offset, int whence);
参数: fd:需要发生偏移的文件的文件描述符 offset:需要偏移的字节数 [+] (往后偏移) [-] (往前偏移) whence: SEEK_SET: 相对于文件的开头。 SEEK_CUR: 相对于当前的位置进行偏移。 SEEK_END: 相对于文件的末尾发生偏移。
返回值: 成功:距离文件开头字节数。 失败:-1
例子:验证lseek的参数。
int main(int argc,char *argv[]) { int fd; fd = open("./test.txt",O_RDWR); //偏移量:0 helloworld int ret; ret = lseek(fd,3,SEEK_SET); //偏移量:3 printf("ret = %d\n",ret);//3 char buf[10] = {0}; read(fd,buf,5); printf("from file:%s\n",buf);//lowor ret = lseek(fd,-3,SEEK_CUR); printf("ret = %d\n",ret);//5 read(fd,buf,5); printf("from file:%s\n",buf);//world close(fd); return 0; } 如果当前偏移量已经在开头,还往前偏移会怎么样? lseek()函数就会返回失败,返回-1,当前的文件定位还在0。
如果当前偏移量已经在文件的末尾,还往后偏移会怎么样? 会发生偏移,但是如果偏移完之后没有写入东西,那么在windows中看到还是在文件末尾。 如果发生偏移之后,写入东西了,那么就会在windows中看到空格。
三、linux系统IO应用实例。 -> LCD液晶屏幕。 1、在linux下,一切都是文件。 连lcd液晶屏幕也是一个文件,既然是一个文件,那么lcd液晶对应的文件名是什么? lcd液晶 -> 硬件设备 -> 去开发板下"/dev"目录下寻找。
/dev/fb0 -> lcd液晶设备 /dev/input/event0 -> 触摸屏设备 /dev/ttySAC0 -> 拓展串口1 /dev/ttySAC1 -> 拓展串口2 /dev/ttySAC2 -> 拓展串口3 /dev/ttySAC3 -> 拓展串口4
2、已知lcd液晶设备名字,就可以使用open函数去访问设备,如果我们要写一些数据(颜色)进去,那么首先必须要了解lcd液晶参数。 1)屏幕尺寸:7寸 2)分辨率:800*480 -> 像素点总数 3)每一个像素点都是由三原色组成的,所以像素点可以显示任何一种颜色。 三原色:红绿蓝。 那么每一个像素点占用多少个字节?
[root@GEC6818 /]#cat /sys/class/graphics/fb0/bits_per_pixel 32 -> 每一个像素点 = 32位 -> 每一个像素点 = 4个字节,分别是ARGB
例子: 尝试写颜色到lcd屏幕上,看看有没有效果?
#include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> #include <unistd.h>
int main(int argc,char *argv[]) { /* 1. 访问lcd液晶屏幕 */ int lcd; lcd = open("/dev/fb0",O_WRONLY); if(lcd < 0) { printf("open lcd error!\n"); } /* 2. 准备颜色,然后写入到lcd设备上 */ int color = 0x00FF0000; //红色 write(lcd,&color,4); /* 3. 关闭文件 */ close(lcd); return 0; }
结果: 整个屏幕的第一个像素点显示为红色。
练习1: 显示满屏的紫色。
#include <stdio.h> #include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> #include <unistd.h>
int main(int argc,char *argv[]) { /* 1. 访问lcd液晶屏幕 */ int lcd; lcd = open("/dev/fb0",O_WRONLY); if(lcd < 0) { printf("open lcd error!\n"); } /* 2. 准备颜色,然后写入到lcd设备上 */ int color = 0x00FF00FF; //紫色 int i; for(i=0;i<800*480;i++) { write(lcd,&color,4); } /* 3. 关闭文件 */ close(lcd); return 0; }
练习2: 验证0x00FFFF00是黄色。 -> 是的 练习3: 黑色和白色是多少? 黑色:0x00000000 白色:0x00FFFFFF
四、内存映射。 1、内存映射方式与普通文件IO方式有什么区别? 例如:想把一些数据写入到文件中。 普通文件IO: open()访问文件 -> 得到一个文件描述符fd -> 直接往文件描述符fd写入数据就可以了 -> 关闭文件描述符fd。
内存映射: open()访问文件 -> 得到一个文件描述符fd -> 根据文件描述符fd去内存空间上映射一块空间,得到一个地址p -> 用户只需要将数据拷贝到内存空间上就可以了 -> 对应的文件就会有相应的变化 -> 撤销映射 -> 关闭文件描述符fd。
2、内存映射主要作用对象:lcd设备。 详细步骤: 1)通过访问文件的方式,得到文件描述符。 int lcd = open("/dev/fb0",O_RDWR);
2)根据文件描述符lcd去内存空间上映射一块空间。 -> mmap() -> man 2 mmap 函数功能:map files or devices into memory //将文件/设备映射到内存空间上
头文件:#include <sys/mman.h> 原型: void *mmap(void *addr, size_t length, int prot, int flags, int fd, off_t offset);
参数: addr:不为NULL -> 用户选择内存空间上的地址。 0.0000001% NULL -> 系统为用户选择空间。 99.99999% length:映射的内存长度 例如:lcd液晶 800*480*4 prot: PROT_EXEC Pages may be executed. PROT_READ Pages may be read. PROT_WRITE Pages may be written. PROT_NONE Pages may not be accessed. 如果需要使用多个权限,则使用"|"连接在一起,例如: PROT_READ|PROT_WRITE flags: MAP_SHARED -> 共享的 MAP_PRIVATE -> 私有的 fd:文件描述符 offset:文件偏移量,(从文件的那个字节开始偏移)
返回值: 成功:指向那片内存空间的区域的地址 失败:-1
3)拷贝数据到空间上。 -> memcpy() -> man 3 memcpy 函数的功能: copy memory area //拷贝数据到内存空间上
头文件: #include <string.h>
原型: void *memcpy(void *dest, const void *src, size_t n);
参数: dest:目标内存空间的地址。 src:需要拷贝的数据(颜色) n:需要拷贝的总字节数。
返回值: 成功:指向dest这个区域的地址 失败:NULL
4)那么对应的文件就会有对应的效果。 5)撤销映射。 -> munmap() -> man 2 munmap 功能: unmap files or devices into memory //撤销映射
头文件: #include <sys/mman.h>
原型: int munmap(void *addr, size_t length);
参数: addr:需要撤销内存空间的地址。 length:需要撤销的长度。
返回值: 成功:0 失败:-1
6)关闭文件。 close(lcd);
例题2:使用内存映射的方式来显示全屏紫色。 #include <stdio.h> #include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> #include <unistd.h> #include <sys/mman.h> #include <string.h>
int main(int argc,char *argv[]) { int lcd,i; int *p = NULL; /* 1. 访问lcd液晶设备 */ lcd = open("/dev/fb0",O_RDWR); if(lcd < 0) printf("open lcd error!\n"); /* 2. 根据文件描述符fd去内存上映射一块区域 */ p = (int*)mmap(NULL,800*480*4,PROT_READ|PROT_WRITE,MAP_SHARED,lcd,0); if(p == (void *)-1) printf("mmap error!\n"); /* 3. 准备颜色 */ int red_color = 0x00FF00FF; /* 4. 将颜色数据拷贝到映射的空间上 */ for(i=0;i<800*480;i++) { memcpy(p+i,&red_color,4); } /* 5. 撤销映射 */ munmap(p,800*480*4); /* 6. 关闭文件 */ close(lcd); return 0; }
练习4: 将int*,修改为char*,程序应该怎么改?
for(i=0;i<800*480;i++) { memcpy(p+i,&red_color,4); } 修改成: for(i=0;i<800*480*4;i+=4) { memcpy(p+i,&red_color,4); }
练习5: 使用内存映射完成以下的效果。