文件数据的I/O,文件偏移,内存映射

这篇博客详细介绍了Linux系统中文件的读写操作,包括read()和write()函数的使用,以及文件偏移量的概念。文件偏移量可以通过lseek()函数进行调整。此外,文章还探讨了内存映射,解释了其与传统文件I/O的区别,并展示了如何使用mmap()、memcpy()和munmap()函数实现内存映射到LCD设备以显示颜色。最后,给出了多个练习帮助读者巩固理解。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

一、文件数据输出/输入。
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*,程序应该怎么改?
   练习5: 使用内存映射完成以下的效果。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值