【Linux】基础IO—1

提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档

目录

文章目录

前言

先来段代码回顾C文件接口

"w"写文件

"a"追加文件

"r"读文件

输出信息到显示器,你有哪些方法

[stdin & stdout & stderr](#stdin & stdout & stderr)

提炼一下对文件的理解(第一阶段)

理解文件并采用系统调用接口来访问文件

总结



前言

世上有两种耀眼的光芒,一种是正在升起的太阳,一种是正在努力学习编程的你!一个爱学编程的人。各位看官,我衷心的希望这篇博客能对你们有所帮助,同时也希望各位看官能对我的文章给与点评,希望我们能够携手共同促进进步,在编程的道路上越走越远!


提示:以下是本篇文章正文内容,下面案例可供参考

先来段代码回顾C文件接口

"w"写文件

cpp 复制代码
#include <stdio.h>
#include <string.h>

int main()
{
	FILE* fp = fopen("myfile", "w");
	if (!fp) {
		printf("fopen error!\n");
	}

	const char* msg = "hello bit!\n";
	int count = 5;
	while (count--) {
		fwrite(msg, strlen(msg), 1, fp);
	}

	fclose(fp);

	return 0;
}

我们要进行文件操作,前提是我们的程序跑起来。文件的打开和关闭,是CPU在执行我们的代码。

以"w"的方式打开文件:

1、如果文件不存在,就在当前路径下,新建指定的文件;

2、默认打开文件的时候,就会先把目标文件清空。

bash 复制代码
echo "hello bite" > log.txt

>(输出重定向):

1、文件不存在,就新建文件;

2、打开文件,先清空,再写入

>>(追加重定向):

不会清空文件,会在文件内容后面添加内容

"a"追加文件

cpp 复制代码
#include <stdio.h>
int main()
{
    // 系统怎么知道当前创建的log.txt文件在这个路径下呢?
    // 因为我们在运行文件操作的时候,执行我们所写的代码,执行的时候,就已经变成了一个进程了,
    // 所以,我们建立log.txt文件时,默认会结合我们当前进程所在路径,拼上我们的log.txt,创建log.txt文件。
    // 我们进程在启动时,所处的路径,叫做当前进程的当前工作路径。
    FILE* fp = fopen("log.txt", "a");
    if (NULL == fp)
    {
        perror("fopen");
        return 1;
    }

    fprintf(fp, "helloworld, %d, %s, %lf\n", 10, "whb", 3.14);

    fclose(fp);
    return 0;
}

"r"读文件

cpp 复制代码
#include <stdio.h>
#include <string.h>

int main()
{
    FILE* fp = fopen("myfile", "r");
    if (!fp) {
        printf("fopen error!\n");
    }

    char buf[1024];
    const char* msg = "hello bit!\n";
    while (1)
    {
        //注意返回值和参数,此处有坑,仔细查看man手册关于该函数的说明
        ssize_t s = fread(buf, 1, strlen(msg), fp);
        if (s > 0) {
            buf[s] = 0;
            printf("%s", buf);
        }
        if (feof(fp)) {
            break;
        }
    }

    fclose(fp);
    return 0;
}

输出信息到显示器,你有哪些方法

cpp 复制代码
#include <stdio.h>
#include <string.h>

int main()
{
    const char* msg = "hello fwrite\n";
    fwrite(msg, strlen(msg), 1, stdout);

    printf("hello printf\n");
    fprintf(stdout, "hello fprintf\n");
    return 0;
}
cpp 复制代码
int fprintf(FILE *stream,const char *format,...)
// 向显示器当中进行打印,把指定的内容按照指定的格式,写到特定的文件当中

stdin & stdout & stderr

  • C默认会打开三个输入输出流,分别是stdin(键盘), stdout(显示器), stderr(显示器)
  • 仔细观察发现,这三个流的类型都是FILE*, fopen返回值类型,文件指针

文件操作详解

提炼一下对文件的理解(第一阶段)

打开文件:本质其实是进程(struct task_struct)打开文件(struct file)!!!

文件没有被打开的时候,在哪里?在磁盘当中。

进程能打开很多文件吗?可以。

系统当中可不可以存在很多进程呢?可以。

很多清空下,OS内部,一定存在大量的被打开的文件。

那么OS要不要把这些被打开的文件进行管理呢?答案是要的。先描述,再组织!所以每一个被打开的文件,在OS内部,一定要存在对应的描述文件属性的结构体。类似PCB。

每一个语言对文件的操作方法都是不一样的。

理解文件并采用系统调用接口来访问文件

a、操作文件,本质:进程在操作文件。进程的文件的关系。

b、文件 -> 磁盘 -> 外设 -> 硬件 -> 向文件中写入,本质是向硬件中写入 -> 用户没有权利直接在硬件内写入 -> OS是硬件的管理者 -> 通过OS写入 -> OS必须给我们提供系统调用接口(OS不相信任何人) -> 我们用的C/C++/...中操作文件的方法,都是对系统调用接口的封装!

操作文件,除了上述C接口(当然,C++也有接口,其他语言也有),我们还可以采用系统接口来进行文件访问, 先来直接以代码的形式,实现和上面一模一样的代码:

cpp 复制代码
 系统调用的文件操作
 man 2 open  2号手册,系统调用 open()函数:打开文件
 #include <sys/types.h>
 #include <sys/stat.h>
 #include <fcntl.h>
 
 int open(const char *pathname,int flags);  // 一般是操作已经存在的文件
 int open(const char *pathname,int flags,mode_t mode);
 第一个参数:打开的文件是谁,可以带路径,也可以直接写文件名;如果只有文件名,就在当前的路径下创建文件;
 第二个参数:想要怎么创建这个文件,flags是一个整数,但是flags可以传递很多标记位;
 第二个参数:flags是一个整数,是32个比特位。用比特位来进行标记位的传递。 ---- OS设计很多系统调用接口的常见方法  本质:位图
 第三个参数:文件的起始权限
 返回值:是一个整数,文件描述符;失败:返回-1,错误码被设置
cpp 复制代码
 man 2 close
 #include <unistd.h>
 int close(int fd); 参数:open()函数返回的整数
cpp 复制代码
 man 2 write  向文件当中写入
 #include <unistd.h>
 ssize_t write(int fd,const void *buf,size_t count);// 把指定的缓冲区写入指定的文件里
 第二个参数:指定一个缓冲区,缓冲区的起始地址
 第三个参数:缓冲区的大小

操作系统为每一个被打开的文件,都创建了内核数据结构(struct file),用于文件的描述,包含了文件的属性;struct file内部中会包含了两个个指针,一个指针会指向我们在系统当中对应的一段与该文件所对应的,叫做文件内核级的缓存(其实就是OS给我们申请的一块内存);另一个指针,指向的是操作底层方法的指针表(比如:一些硬件设备的操作方法)。

文件 = 属性 + 内容

  • 属性初始化 struct file
  • 文件内容写到缓存里:日后读、写就在缓存里进行,然后再把数据刷新到磁盘里。

所以,每一个文件都要有一个:文件内核级的缓存
最终我们对OS内的文件的管理,转化成了对 struct file的内核数据结构的管理。
进程task_struct里有一个指针:struct files_struct *files,这个指针指向的是struct files_struct空间,空间里有一个指针数组(struct file *fd arrayN),这个指针数组中的每一个元素都是被打开文件的地址,而指针数组的下标是fd(文件描述符)。

操作系统只认文件描述符fd,只要拿到了fd就可以对文件进行操作。

无论读写,都必须在合适的时候,让OS把文件的内容读到文件缓存区中。write、read函数,本质都是在拷贝数据。

open()在干什么呢?

1、创建file;

2、开辟文件缓冲区的空间,加载文件数据(延后);

3、查进程的文件描述符表;

4、file地址,填入对应的文件描述符表下标中;

5、返回下标。

cpp 复制代码
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>

int main()
{
    const char* message = "hello Linux file!\n";
    // 可以直接向"1"里面打印内容,"1"就是标准输出流
    write(1, message, strlen(message));

    // C语言的方式向标准输出流里打印内容
    fprintf(stdout, "hello: %d\n", 10);// 第一个参数是stdout的话,和printf()函数的结果一样
    fflush(stdout);// 刷新数据

    // 0:标准输入(键盘)  1:标准输出(显示器)  2:标准输出(显示器) 
    // 0、1、2已经被占用了,所以open()函数的返回值从3开始
    int fda = open("loga.txt", O_WRONLY | O_CREAT | O_TRUNC, 0666);
    printf("fda: %d\n", fda);// 3
    int fdb = open("logb.txt", O_WRONLY | O_CREAT | O_TRUNC, 0666);
    printf("fdb: %d\n", fdb);// 4
    int fdc = open("logc.txt", O_WRONLY | O_CREAT | O_TRUNC, 0666);
    printf("fdc: %d\n", fdc);// 5
    int fdd = open("logd.txt", O_WRONLY | O_CREAT | O_TRUNC, 0666);
    printf("fdd: %d\n", fdd);// 6

    umask(0);// 权限掩码设置为0  就近原则:有设置的掩码,就用设置的;没有,就用系统的
    // system call
    // O_WRONLY:以只写的方式打开    O:代表open的意思
    // O_CREAT:如果文件不存在,创建这个文件
    // O_TRUNC:清空文件内容
    // O_APPEND:打开文件,用追加模式
    // 0666:文件的起始权限,起始权限也会和umask权限掩码处理,所以,不一定是0666的权限
    int fd = open("log.txt", O_WRONLY | O_CREAT | O_TRUNC, 0666);
    int fd = open("log.txt", O_WRONLY | O_CREAT | O_APPEND, 0666);
    // open()系统调用接口,以写的方式打开,默认不清空文件内容,下次写入文件是以覆盖式的写入内容

    if(fd < 0)
    {
        perror("open");
        return 1;
    }

    const char *message = "hello Linux file!\n";
    //const char *message = "abcdefg\n";
    //const char *message = "123";
    write(fd, message, strlen(message));// C语言中有'\0',而Linux中没有'\0'的规则

    close(fd);
    return 0;
}

O_RDONLY: 只读打开

O_WRONLY: 只写打开

O_RDWR : 读,写打开 这三个常量,必须指定一个且只能指定一个

O_CREAT : 若文件不存在,则创建它。需要使用mode选项,来指明新文件的访问权限 O_APPEND: 追加写

怎么理解write(1, message, strlen(message))向一个整数里写,就相当于在一个文件里写呢?

文件描述符fd,fd的本质是:内核的进程和文件映射关系的数组的下标。

cpp 复制代码
 man 2 umask  //2号手册的权限掩码

 #include <sys/types.h>
 #include <sys/stat.h>
 mode_t umask(mode_t mask);//在程序运行时,动态的设置权限的掩码

我们这样讲可能还会有些抽象,我们直接找到原码来看一下这些结构体之间的关系:

而现在知道,文件描述符就是从0开始的小整数。当我们打开文件时,操作系统在内存中要创建相应的数据结构来 描述目标文件。于是就有了file结构体。表示一个已经打开的文件对象。而进程执行open系统调用,所以必须让进 程和文件关联起来。每个进程都有一个指针*files, 指向一张表files_struct,该表最重要的部分就是包涵一个指针数 组,每个元素都是一个指向打开文件的指针!所以,本质上,文件描述符就是该数组的下标。所以,只要拿着文件 描述符,就可以找到对应的文件。

我们要对文件进行操作,我们可以用系统调用,也可以用语言提供的文件方法,但是最好还是用语言提供的文件方法。为什么呢?

因为系统不同,系统调用接口可能不一样,系统调用的方法不具有跨平台性。

语言为什么具有跨平台性?

比如:C语言文件的操作函数,底层都是用系统调用接口实现的,而不同的操作平台,系统调用接口可能不一样,就比如:C语言中的fopen()函数就使用不同的OS(windows、maxos、Linux)的系统调用接口实现fopen()函数,那么在编译的时候,不同的OS会生成不同的标准库,当然这些标准库,也都是C标准库,我们每再一个平台下,都需要下载对应平台的标准库,它们标准库中实现的函数都叫fopen(),所以C语言具有跨平台性。

我有一个显示器,但是可以有很多终端。

bash 复制代码
ls /lib64/libc.so 
//C语言的动态库(文件)

**文件描述符的分配规则:**在files_struct数组当中,找到当前没有被使用的 最小的一个下标,作为新的文件描述符。


总结

好了,本篇博客到这里就结束了,如果有更好的观点,请及时留言,我会认真观看并学习。
不积硅步,无以至千里;不积小流,无以成江海。

相关推荐
A小辣椒13 小时前
TShark:Wireshark CLI 功能
linux
A小辣椒17 小时前
TShark:基础知识
linux
AlfredZhao19 小时前
OCI 明明分配了 200G 系统盘,为什么 df 只看到 30G?
linux·oci
AlfredZhao1 天前
vi 删除指定范围的行,不用再反复按 dd
linux·vi
用户9718356334662 天前
银河麒麟 KY10 申威(SW64) 安装 nginx-1.16.1-2.p01.ky10.sw_64.rpm 详细步骤
linux
猪脚踏浪2 天前
linux 拷贝文件或目录到指定的位置
linux
大树882 天前
金刚石散热越强,管路越先见顶
大数据·运维·服务器·人工智能·ai
摇滚侠2 天前
Linux CentOS7 rpm 安装 MySQL 5.7
linux·运维·mysql
霸道流氓气质2 天前
领域驱动设计(DDD)在 Spring Boot 微服务中的实践指南
运维·spring boot·微服务
bush42 天前
嵌入式linux学习记录十四、术语
linux·嵌入式