LinuxC——1.文件读写

时光怂恿深爱的人放手 提交于 2020-04-05 17:27:02

LinuxC——1.文件读写

1.❤️文件IO

从CPU到文件是Output的一个过程,从文件到CPU是一个Input的过程,这个过程是以CPU为点的

2.🧡系统函数

  • open:打开文件
  • close:关闭文件
  • read:读数据
  • write:写数据
  • lseek:移动文件中读写位置
  • dup:文件书写位置重定位函数,重定位可以写入另一个文件
  • fcntl:文件描述符设置
  • ioctl:一个特殊函数

3.💛文件读写的简单例子

  • open函数:通过fd,找到块设备文件
    • 文件系统是一个程序代码,组织块设备所有文件
    • 文件系统属于OS一部分
    • 找到文件后,调用块设备驱动,打开文件
      • 打开成功,返回非负操作符
      • 打开失败,返回-1
  • write函数:利用打开成功返回的,向文件里面写数据
  • lseek函数:利用文件描述符,将文件读写位置调整到文件相应位置
    • why设置文件头
    • write的时候,文件读写位置已经到了末尾
  • read函数:从文件头开始,读取指定长度的数据到buf中
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>
int main() {
        int fd = 0;

        fd = open("./file.txt", O_RDWR);
        if(-1 == fd) {
                printf("open fail\n");
                return 1;
        }else {
                printf("open ok\n");
        }

        char buf[] = "Steve Yu, today is nice";
        write(fd, (void *)buf, strlen(buf));

        lseek(fd, SEEK_SET, 0);

        char buf2[30] = {0};
        read(fd, buf2, sizeof(buf));

        puts(buf2);
  
  			close(fd);
        return 0;
}

4.💚文件打开后,OS做了什么?

  1. 记录打开文件信息

    1. 程序运行后是一个进程,OS会创建一个task_struct的结构体,记录运行的各种信息
    2. open将文件打开后,在task_struct会创建一些数据结构,用来创建当前进程的打开文件的信息,后面所有的都依赖这些信息
  2. open函数会申请一段内存空间(内核缓存)

    1. 内核缓存是在OS的一个空间,比如char buf[100],就开辟了缓存
    2. 由os开辟的叫内核缓存
  3. open为啥要开辟内存缓存空间?

    内存读写 > 磁盘读写,我们会省时间

  4. open仅仅开辟了内存缓存空间,并没有数据,只有读写数据的时候

    先将数据读到内核缓存中,之后下层会让内存缓存和磁盘数据进行交换

  5. 读写的时候,数据的流动?

    硬件 -> 驱动缓存 -> 内核缓存 -> 应用程序中的数组

(仅掌握open函数,其余函数已经过时)

5.💙指定宏

  1. O_RDONLY:只读
  2. O_WRONLY:只写
  3. O_RDWR:可读可写

以上三个不可以用 | 进行宏运算,而可以与以下进行宏运算

  1. O_TRUNC:打开进行清空
  2. O_APEND:打开后写数据追加
  3. O_NONBLOCK和O_AYNC:非阻塞,同步

flag参数之O_CREAT、O_EXCL

  1. O_CREAT:新建一个文件
  2. O_EXCL:O_EXCL和O_CREAT同时被指定,打开文件,如果文件存在,就报错

可以使用O_RDWR|O_CREAT进行组合,那么可以不存在进行创建,存在不打开的功能

6.💜文件描述符

  1. 文件描述符是什么?

文件描述符指向打开的文件,后面read/write/close等操作,都是基于文件描述符进行操作

  1. 文件描述符池

每个程序运行起来后,就是一个进程,系统给每个进程分配01023的描述符的范围,返回的文件描述符,是在01023的某个数字

  1. 为啥第一个打开的文件,open返回的是3?

open返回的文件描述符是规则的:

  • open返回文件描述符池中,返回当前没用的最小的一个

  • 进程运行起来,0/1/2会默认使用,最小没用的是3

  1. fopen文件指针

fopen是C库标准io函数

fopen成功后,FILE*的文件指针,打开了文件

fopen成功后,返回的是FILE*文件指针,指向打开的文件

  1. 对于Linux C库,fopen对open进行二次封装

7.❤️errno和perror

Linux中,如果像上述中,打开失败,那么就直接失败,遇到比较难排查的错误原因,难以查处具体错误

  1. 什么是errno?

errno我们查看man 3 errno中,可以看到,一系列错误宏定义,我们include "errno.h"即可

printf("%d: open error", errno);

这样即可发现17:open error,打开错误

2.具体错误

我们仅仅知道错误号,不知道具体错误,怎么办呢?

  • perror函数:我们通过man 3 perror,可以查看到错误号的具体使用

    perror可以打印的错误号以及具体错误

perror("open fail");

我们可以知道,控制台打印:open fail: File exists

3.使用man 2 open进行查看ERRORS下错误号

比如EACCES,不允许访问

我们因为记不住错误号,也记不住宏定义,所以,我们有了perror

8.🧡close、write、read

  1. close函数

close(fd):不主动关闭,应用也会自动关闭fd,但是我们得进行手动关闭

  1. close函数做了啥
  • open打开的时候会在task struct中创建结构体空间,如果文件关闭,那么该结构体空间被释放

    类似free(空间地址)

  • malloc和free是给C调用的库看书,Linux在释放的时候,有自己的释放函数

好习惯必须关闭,否则我们在生产环境,容易内存溢出

9.💛task struct结构体

  1. 结构体在存在在运行的进程中,在task struct是程序在运行的时候进行开辟

  2. 进程运行结束,linux系统会调用自己的系统函数,进行释放task struct

10.💚文件描述符

有关0/1/2文件描述符

  • stdin,文件描述符为0,0代表键盘,实现键盘输入

键盘->键盘驱动缓存->内核缓存->应用缓存

#include <unistd.h>
#include <stdio.h>
int main() {
        int ret = 0;
        char buf[30] = {0};
        ret = read(0, (void *)buf, 30);
        if(-1 == ret) {
                perror("read fail");
                return -1;
        }
        puts(buf);
        return 0;
}

scanf底层调用read(0, buf, size)这个,这样就能兼容不同操作系统

  • close(0),scanf还能工作不?

答案是不可以,用perror打印,会得到bad description的

  • stdout,文件描述符为1,使用1,则可以进行显示器显示
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
int main() {
        int fd = 1, ret = 0;

        char buf[] = "steve\n";
        write(fd, buf, sizeof(buf));

        close(fd);
        return 0;
}

假设我们write(1, &a, sizeof(a)), a是一个int值,那么会打印一个(char)a进行

  • close(1)

printf不能工作

  • stderr, 文件描述符是2,是标准出错输出

使用2文件描述符也能进行打印

1打印普通信息,2打印报错信息

  • close(2)

perror是通过2进行打印,如果close(2),则不能进行perror

  • 宏定义

0:STDIN_FILENO

1:STDOUT_FILENO

2:STDERR_FILENO

11.💙lseek

lseek用来调整读写的位置,调用成功返回偏移量,调用失败返回-1

我们可以通过lseek获取文件大小

1.fd:打开文件

2.whence:粗定位

SEEK_SET:起始位置

SEEK_CUR:当前读写位置

SEEK_END:文件末尾位置

3.offset:微调

进行偏移,正数向前移动,负数向后

#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>

int main() {
        int fd = 0, ret = 0;

        fd = open("./file.txt", O_RDONLY);

        if(-1 == fd) {
                perror("file open failed");
                exit(-1);
        }

        ret = lseek(fd, 0, SEEK_END);

        printf("文件大小%d\n", ret);
        return 0;
}

4.使用od -c filename,可以查看以字符进行显示字符

12.💜进程表和文件描述表

进程表:task_struct

  • 这个结构体成员多达300个
  • 每个进程运行起来后,linux系统会开辟task_struct 结构体
  • task_struct专门用于进程运行中,涉及进程相关信息

13.❤️共享操作文件

  • 同一个进程共享相同文件
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdlib.h>
#include <unistd.h>
void print_error(char* str) {
        perror(str);
        exit(-1);
}
int main() {
        int fd1, fd2;

        fd1 = open("./file.txt", O_RDWR);

        if(-1 == fd1) print_error("open fail");

        fd2 = open("./file.txt", O_RDWR);

        if(-1 == fd2) print_error("open fail");
  
  			// 写入操作
        return 0;
}

存在相互覆盖的关系

解决方案:使用O_APPEND进行追加

  • 多个进程共享相同文件
gcc share_op_file.c -o a
gcc share_op_file.c -o b
./a
./b

那么会出现覆盖情况

解决方案:也使用O_APPEND进行追加

14.🧡dup和dup2

  1. int dup(int fd)
  • 在unistd.h中

  • dup用来复制文件描述符,得到一个新的文件描述符,指向原来的文件,指向最小没用的那一个

fd2 = dup(fd1)
  1. int dup2(int oldfd, int newfd)
  • 在unistd.h中
  • dup2指定一个文件描述符,用来复制文件描述符,得到一个新的文件描述符,如果已经打开,则关闭后再次打开
fd2 = (fd1, 4)
  1. dup、dup2的意义

实现文件共享操作,不使用APPEND也不会出现文件覆盖。使用dup或dup2的时候,永远只有一个文件表。

15.💛实现文件重定位

步骤:

  1. 打开文件fd

  2. close(1)

  3. 进行复制fd到1

#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>
#include <unistd.h>
int main() {
        int fd1;
        fd1 = open("data.txt", O_RDWR|O_CREAT);
        close(1);
        dup(fd1);
        printf("Hello World");
        return 0;
}

当我们文件描述符写死了,那么可以进行文件重定位来进行,linux底层的重定位>是使用dup/dup2作为底层的支持

16.fcntl函数

int fcntl(int fd, int cmd, ... /* arg */ );

fcntl是文件控制函数(file control)

  • fd:指向打开文件

  • cmd:控制命令

    • F_DUPFD
      • 模拟DUP
    • F_GETFL、F_SETFL
      • 获取设置文件状态标注,比如open没有指定O_APPEND,可以使用fcntl进行补设
    • F_GETFD、F_SETFD(文件描述符)
    • F_GETOWN、F_SETOWN(OWN)
    • F_GETLK、F_SETLK、F_SETLKW(加锁)

    先掌握前两个F_DUPFDF_GETFL、F_SETFL

模拟DUP:

fcntl(fd, F_DUPFD, 0);// 第三个参数没有,用0表示

模拟DUP2:

fcntl(fd, F_DUPFD, 1);// 模拟DUP2,进行用1

设置

fcntl(fd, F_SETFL, O_RDWR|O_APPEND); // 修改打开时的Flag,返回新设置的标志

保留原标志,加新标志

flag = fcntl(fd, F_GETFL); // 获取
fcntl(fd, F_SETFL, flag|O_APPEND); // 叠加
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!