热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

【Linux】基础IO——上

🎇Linux:基础IO详解博客主页:一起去看日落吗分享博主的在Linux中学习到的知识和遇到的问题博主的能力有限,出现错误





🎇Linux:基础IO详解




  • 博客主页:一起去看日落吗
  • 分享博主的在Linux中学习到的知识和遇到的问题
  • 博主的能力有限,出现错误希望大家不吝赐教
  • 分享给大家一句我很喜欢的话: 看似不起波澜的日复一日,一定会在某一天让你看见坚持的意义,祝我们都能在鸡零狗碎里找到闪闪的快乐🌿🌞🐾。



在这里插入图片描述


✨ ⭐️ 🌟 💫



目录


  • 💫 1. 简单复习文件操作
    • 🌟 1.1 写文件
    • 🌟 1.2 读文件
    • 🌟 1.3 追加文件
    • 🌟 1.4 一切皆文件

  • 💫 2. 系统文件I/O
    • 🌟2.1 open
    • 🌟2.2 close
    • 🌟2.3 read
    • 🌟2.4 write
    • 🌟2.5 测试用例

  • 💫 3. 文件描述符fd
    • 🌟 3.1 文件描述符的分配规则

  • 💫 4. 重定向原理
  • 基础IO上总结



💫 1. 简单复习文件操作

🌟 1.1 写文件

如果以"w"模式打开文件,默认是文本读写,且会把原始内容清掉再写。

#include
int main()
{
FILE* fp = fopen("./log.txt", "w");//以写的方式打开当前目录下的log.txt文件,没有就新建文件,如果目标文件存在,w写时会清空目标文件
//FILE* fp = fopen("log.txt", "w");//没有./,它默认是在当前路径下新建文件
if(fp == NULL)
{
perror("fopen");
return 1;
}
int count = 0;
while(count < 10)
{
fputs("hello byih\n", fp);//往log.txt文件中写数据
count&#43;&#43;;
}
fclose(fp);//关闭文件
return 0;
}

FILE* fp &#61; fopen(“log.txt”, “w”);

虽然没有 ./ 指定路径&#xff0c;但是它还是在当前路径下新建文件了&#xff0c;因为每个进程都有一个内置的属性 cwd(可以在 /proc 目录下查找对应进程的属性信息)&#xff0c;cwd 可以让进程知道自己当前所处的路径&#xff0c;这也解释了在 VS 中不指明路径&#xff0c;它也能新建对应的文件在对应的路径&#xff0c;换言之&#xff0c;进程在哪个路径运行&#xff0c;文件的新建就哪个路径。

在这里插入图片描述




&#x1f31f; 1.2 读文件

fgets从特定文件流中按行读取&#xff0c;内容放在缓冲区。读取成功返回字符串起始地址&#xff0c;读失败返回NULL.

#include
int main()
{
FILE* fp &#61; fopen("./log.txt", "r");//以读的方式打开当前目录下的log.txt文件,没有就报错
if(fp &#61;&#61; NULL)
{
perror("fopen");
return 1;
}
int count &#61; 0;
char buffer[128];
while(count < 10)
{
fgets(buffer, 128, fp);//从log.txt文件中读128个字符到buffer,\n会使fgets停止读取
printf("%s\n", buffer);
count&#43;&#43;;
}
fclose(fp);//关闭文件
return 0;
}



&#x1f31f; 1.3 追加文件

#include
#include
int main()
{
FILE* fp &#61; fopen("./log.txt", "a");//以追加的打开当前目录下的log.txt文件,没有就新建,如果目标文件存在,a写时不会清空目标文件,在文件内容最后写入
if(fp &#61;&#61; NULL)
{
perror("fopen");
return 1;
}
const char* msg &#61; "Hello DanceBit\n";
//fwrite(msg, strlen(msg) &#43; 1, 1, fp);//乱码
fwrite(msg, strlen(msg), 1, fp);
fclose(fp);
return 0;
}

size_t fwrite ( const void* ptr, size_t size, size_t count, FILE* stream );

size 表示你要写入的基本单元是多大(以字节为单位)&#xff0c;count 表示你要写入几个这样的基本单元。

fwrite(msg, strlen(msg) &#43; 1, 1, fp);

strlen(msg) &#43; 1 -> 乱码&#xff0c;也就是把 \0 也追加会造成&#xff0c;因为 \0 是 C 的规定&#xff0c;和文件无关。这里 cat log.txt 并没有看到乱码的原因是 \0 是不可见的&#xff0c;所以这里 vim log.txt 才可以看到乱码。




&#x1f31f; 1.4 一切皆文件

C语言默认会打开三个输入输出流&#xff1a;stdin、stdout、stderr&#xff0c;它们的类型都是FILE*&#xff0c;C语言把它们当做文件看待&#xff1b;站在系统角度&#xff0c;stdin对应的硬件设备是键盘、stdout对应显示器、stderr对应显示器&#xff0c;本质上我们最终都是访问硬件。C&#43;&#43;中也有cin、cout、cerr&#xff0c;几乎所有语言都提供标准输入、标准输出、标准错误。

请添加图片描述
默认情况下&#xff0c;标准输入是键盘文件&#xff0c;标准输出是显示器文件&#xff0c;标准错误是显示器文件。而这三个本身是硬件&#xff0c;如何理解 Linux 中&#xff0c;一切皆文件&#xff1f;

所有的外设硬件&#xff0c;本质对应的核心操作无外乎是 read 或 write。对于键盘文件&#xff0c;它的读方法就是从键盘读取数据到内存&#xff0c;对于显示器文件&#xff0c;如调用 printf 函数时&#xff0c;操作系统是要往显示器上写入的&#xff0c;其实你输入的命令是你通过键盘输入的&#xff0c;所以系统应该是往键盘读数据。至于用户能看到输入的命令&#xff0c;仅仅是为了方便用户&#xff0c;操作系统把从键盘输入的数据&#xff0c;一方面给了系统读取&#xff0c;一方面给显示器方便用户。所以不同的硬件&#xff0c;对应的读写方式肯定是不一样的&#xff0c;但是它们都有 read 和 write 方法&#xff0c;换言之&#xff0c;这里的硬件可以统一看作一种特殊的文件。比如这里设计一种结构叫做 struct file&#xff0c;它包括文件的属性、文件的操作或方法等。

Linux下的六字真言&#xff1a;先描述&#xff0c;在组织

组织就是要把每一个硬件对应的结构体关联起来&#xff0c;并用 file header 指向。所以在操作系统的角度&#xff0c;它看到的就是一切皆文件&#xff0c;也就是说所有硬件的差异&#xff0c;经过描述&#xff0c;就变成了同一种东西&#xff0c;只不过当具体访问某种设备时&#xff0c;使用函数指针执行不同的方法&#xff0c;就达到了不同的行为。

#include
#include
int main()
{
const char* msg &#61; "Hello DanceBit\n";
fwrite(msg, strlen(msg), 1, stdout);
char buffer[64];
fread(buffer, 1, 10, stdin);//你输入时没有写\0,fread时也不会加,所以一旦超过10,就会出现乱码
buffer[10] &#61; &#39;\0&#39;;
printf("%s\n", buffer);
return 0;
}

这里可以直接使用 fwrite 这样的接口&#xff0c;向显示器写数据的原因是因为 C 程序一运行&#xff0c;stdout 就默认打开了。同理 fread 能从键盘读数据的原因是 C 程序一运行&#xff0c;stdin 就默认打开了。

也就是说 C 接口除了对普通文件进行读写之外(需要打开)&#xff0c;还可以对 stdin、stdout、stderr 进行读写(不需要打开)。

scanf -> 键盘、printf -> 显示器、perror -> 显示器




&#x1f4ab; 2. 系统文件I/O

如上我们知道&#xff0c;这些文件操作最终都是访问硬件(显示器、键盘、文件(磁盘))。众所周知&#xff0c;OS是硬件的管理者。所有语言上对“文件”的操作&#xff0c;都必须贯穿操作系统。然而OS不相信任何人&#xff0c;访问操作系统&#xff0c;就必须要通过系统接口&#xff01;&#xff01;

其实我们学过的几乎所有的语言中&#xff0c;fopen/fclose&#xff0c;fread/fwrite&#xff0c;fputs/fgets&#xff0c;fgets/fputs 等底层一定需要使用OS提供的系统调用接口&#xff0c;下面咱们就来学习文件的系统调用接口

在这里插入图片描述


&#x1f31f;2.1 open

#include
#include
#include
#include
#include
#include
int main()
{
int fd &#61; open("log.txt", O_WRONLY|O_CREAT, 0644);//打开
if(fd < 0)
{
perror("open");
return 1;
}
//操作
const char* byh &#61; "Hello System Call!\n";
write(fd, byh, strlen(byh));
write(fd, byh, strlen(byh));
write(fd, byh, strlen(byh));
write(fd, byh, strlen(byh));

close(fd);//关闭
return 0;
}

使用 open 需要包含三个头文件&#xff0c;它有两个版本。版本一&#xff1a;以 flags 方式打开 pathname&#xff1b;版本二&#xff1a;以 flags 方式打开 pathname&#xff0c;并设置 mode 权限。
请添加图片描述

pathname: 要打开或创建的目标文件文件名
flags: 打开方式。传递多个标志位&#xff0c;下面的一个或者多个常量进行“或”运算&#xff0c;构成flags.
O_RDONLY: 只读打开
O_WRONLY: 只写打开
O_RDWR : 读写打开
以上这三个常量&#xff0c;必须指定一个且只能指定一个
O_CREAT : 若文件不存在&#xff0c;则创建它。同时需要使用mode选项&#xff0c;来指明新文件的访问权限
O_APPEND: 追加写
mode: 设置默认权限信息

flags 可以是 O_RDONLY(read-only)、O_WRONLY(write-only)、O_RDWR(read/write)&#xff0c;且必须包含以上访问模式之一。此外访问模式还可以带上 |标志位&#xff0c;下面会介绍一两个标志位&#xff0c;实际还要看场景使用。

以写的方式打开一个存在的文件&#xff0c;它同 fopen 一样&#xff0c;如果没有写操作&#xff0c;原文件的内容不会被覆盖&#xff1b;如果写操作&#xff0c;原文件的内容会被覆盖成写的内容。

以写的方式打开不存在的文件&#xff0c;权限是 644&#xff0c;运行程序发现没有新建文件 。

O_CREATE 发现文件不存在&#xff0c;将会新建文件&#xff0c;且必须指定 mode 权限(如果没有指定&#xff0c;那么新建的文件会变成可执行程序)&#xff0c;如果没有 O_CREATE&#xff0c;说明文件是存在的&#xff0c;则可忽略 mode 权限(就算写了权限也不会对原来的文件更改权限)。




&#x1f31f;2.2 close

请添加图片描述
使用 close 关闭文件&#xff0c;需要包含 unistd 头文件。fd 是 open 的返回值。




&#x1f31f;2.3 read

要使用 read 读文件&#xff0c;需要包含 unistd 头文件。read 从 fd 文件描述符中读数据到 buf&#xff0c;读 count 个字节&#xff0c;返回值是实际读到的数据。

请添加图片描述

#include
#include
#include
#include
#include
int main()
{
int fd &#61; open("log.txt", O_RDONLY);//打开
if(fd < 0)
{
perror("open");
return 1;
}
//操作
char buffer[1024];
ssize_t sz &#61; read(fd, buffer, sizeof(buffer) - 1);//期望读1023个,但实际可能只有100个,是从文件读,文件并不遵守字符串\0的规则,所以要主动\0
if(sz > 0)
{
buffer[sz] &#61; &#39;\0&#39;;//利用read的返回值,实际读到的个数就是该被\0的位置
printf("%s\n", buffer);
}
close(fd);//关闭
return 0;
}



&#x1f31f;2.4 write

使用 write 写入文件&#xff0c;需要包含 unistd 头文件。write 向 fd 文件描述符中写入 buf&#xff0c;写 count 个字节&#xff0c;返回值是写了多少个

请添加图片描述




&#x1f31f;2.5 测试用例

#include
#include
#include
#include
#include
#include
int main()
{
int fd &#61; open("log.txt", O_WRONLY|O_APPEND);//打开
if(fd < 0)
{
perror("open");
return 1;
}

//操作
const char* byh &#61; "Hello System Call!\n";
write(fd, byh, strlen(byh));
write(fd, byh, strlen(byh));

close(fd);//关闭

return 0;
}

请添加图片描述




&#x1f4ab; 3. 文件描述符fd

#include
#include
#include
#include

int main()
{
int fd1 &#61; open("log1.txt", O_WRONLY|O_APPEND|O_CREAT, 0644);
int fd2 &#61; open("log2.txt", O_WRONLY|O_APPEND|O_CREAT, 0644);
int fd3 &#61; open("log3.txt", O_WRONLY|O_APPEND|O_CREAT, 0644);
int fd4 &#61; open("log4.txt", O_WRONLY|O_APPEND|O_CREAT, 0644);
int fd5 &#61; open("log5.txt", O_WRONLY|O_APPEND|O_CREAT, 0644);
printf("fd1: %d\n", fd1);
printf("fd2: %d\n", fd2);
printf("fd3: %d\n", fd3);
printf("fd4: %d\n", fd4);
printf("fd5: %d\n", fd5);

return 0;
}

请添加图片描述
我们说过返回小于 0 的数&#xff0c;则代表 open 失败&#xff0c;显示这里 open 都成功了。但是这里为什么不从 0 开始依次返回&#xff1f;—— 上面我们说过 C 程序运行起来&#xff0c;默认会打开三个文件(stdin、stdout、stderr)&#xff0c;所以 0, 1, 2 分别与之对应。

Linux进程默认情况下会有3个缺省打开的文件描述符&#xff0c;分别是标准输入0&#xff0c; 标准输出1&#xff0c; 标准错误2.

0,1,2对应的物理设备一般是&#xff1a;键盘&#xff0c;显示器&#xff0c;显示器
在这里插入图片描述

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




&#x1f31f; 3.1 文件描述符的分配规则

#include
#include
#include
#include
#include
int main()
{
//close(0);
close(2);
int fd &#61; open("myfile", O_RDONLY);
if(fd < 0){
perror("open");
return 1;
}

printf("fd: %d\n", fd);

close(fd);
return 0;
}

请添加图片描述

每次给新文件分配的fd&#xff0c;是从fd_array[]中找一个最小的、未被使用的作为新的fd.

这其实很好理解&#xff0c;打开的文件要和进程产生关联&#xff0c;就要线性遍历数组中找一个未被使用的下标&#xff0c;填入文件地址。




&#x1f4ab; 4. 重定向原理

#include
#include
#include
#include
#include
int main()
{
//close(0);
close(1);
int fd1 &#61; open("log3.txt", O_CREAT|O_WRONLY, 0644);
int fd2 &#61; open("log4.txt", O_CREAT|O_WRONLY, 0644);
printf("hello byh!: %d\n", fd1);
printf("hello byh!: %d\n", fd2);
fflush(stdout);

close(fd1);
close(fd2);
return 0;
}

请添加图片描述
要想看到数据也很简单&#xff0c;在 close 之前 fflush 强制刷新即可&#xff0c;但这里要注意 fd1 和 fd2 对应 1 和 3&#xff0c;它们都是磁盘文件&#xff0c;printf 时&#xff0c;因为缓冲区没满&#xff0c;所以都在语言层的缓冲区&#xff0c;但是 fflush 之后&#xff0c;就会一次性的把两次数据都往 fd1 指向的文件中刷新。本来 printf 应该往显示器上输出&#xff0c;但 close 1&#xff0c;open 新文件&#xff0c;导致 1 的指向由显示器转换为磁盘&#xff0c;导致最终往文件里输出&#xff0c;本质重定向改变的是底层文件描述符下标指针的内容&#xff0c;上层是不知道的&#xff0c;这种技术叫做输出重定向。

虽然 stdout 和 stderr 对应的设备都是显示器&#xff0c;但是它们是两个独立的文件描述符&#xff0c;且作用却大不相同。这里虽然它们最终都往显示器上输出&#xff0c;但是重定向时&#xff0c;却只能对 stdout 重定向&#xff0c;因为底层改的是 1&#xff0c;没有影响 2

在这里插入图片描述




基础IO上总结
  • 如何理解—— 一切皆文件。
  • 进程在启动时&#xff0c;默认会打开 0, 1, 2&#xff0c;对应 C 语言上&#xff0c;就是 stdin, stdout, stderr。
  • 库函数的文件操作和系统调用的文件操作。
  • FILE* 和 fd。
  • fd 本质是进程和文件之间对应关系的数组的下标&#xff0c;有了 fd&#xff0c;就可以找到&#xff0c;打开文件的所有细节。
  • FILE*&#xff0c;FILE 是一个结构体&#xff0c;它主要有两块重要的成员 _fileno、缓冲区。
  • 数据在文件层面的流动过程。
  • 初步了解了重定向的原理和现象。








推荐阅读
  • 本文介绍了设计师伊振华受邀参与沈阳市智慧城市运行管理中心项目的整体设计,并以数字赋能和创新驱动高质量发展的理念,建设了集成、智慧、高效的一体化城市综合管理平台,促进了城市的数字化转型。该中心被称为当代城市的智能心脏,为沈阳市的智慧城市建设做出了重要贡献。 ... [详细]
  • 本文讨论了clone的fork与pthread_create创建线程的不同之处。进程是一个指令执行流及其执行环境,其执行环境是一个系统资源的集合。在调用系统调用fork创建一个进程时,子进程只是完全复制父进程的资源,这样得到的子进程独立于父进程,具有良好的并发性。但是二者之间的通讯需要通过专门的通讯机制,另外通过fork创建子进程系统开销很大。因此,在某些情况下,使用clone或pthread_create创建线程可能更加高效。 ... [详细]
  • mac php错误日志配置方法及错误级别修改
    本文介绍了在mac环境下配置php错误日志的方法,包括修改php.ini文件和httpd.conf文件的操作步骤。同时还介绍了如何修改错误级别,以及相应的错误级别参考链接。 ... [详细]
  • Linux服务器密码过期策略、登录次数限制、私钥登录等配置方法
    本文介绍了在Linux服务器上进行密码过期策略、登录次数限制、私钥登录等配置的方法。通过修改配置文件中的参数,可以设置密码的有效期、最小间隔时间、最小长度,并在密码过期前进行提示。同时还介绍了如何进行公钥登录和修改默认账户用户名的操作。详细步骤和注意事项可参考本文内容。 ... [详细]
  • c语言\n不换行,c语言printf不换行
    本文目录一览:1、C语言不换行输入2、c语言的 ... [详细]
  • 本文介绍了为什么要使用多进程处理TCP服务端,多进程的好处包括可靠性高和处理大量数据时速度快。然而,多进程不能共享进程空间,因此有一些变量不能共享。文章还提供了使用多进程实现TCP服务端的代码,并对代码进行了详细注释。 ... [详细]
  • 本文介绍了解决二叉树层序创建问题的方法。通过使用队列结构体和二叉树结构体,实现了入队和出队操作,并提供了判断队列是否为空的函数。详细介绍了解决该问题的步骤和流程。 ... [详细]
  • Linux环境变量函数getenv、putenv、setenv和unsetenv详解
    本文详细解释了Linux中的环境变量函数getenv、putenv、setenv和unsetenv的用法和功能。通过使用这些函数,可以获取、设置和删除环境变量的值。同时给出了相应的函数原型、参数说明和返回值。通过示例代码演示了如何使用getenv函数获取环境变量的值,并打印出来。 ... [详细]
  • 本文介绍了在mac环境下使用nginx配置nodejs代理服务器的步骤,包括安装nginx、创建目录和文件、配置代理的域名和日志记录等。 ... [详细]
  • 怎么在PHP项目中实现一个HTTP断点续传功能发布时间:2021-01-1916:26:06来源:亿速云阅读:96作者:Le ... [详细]
  • 本文介绍了在CentOS 6.4系统中更新源地址的方法,包括备份现有源文件、下载163源、修改文件名、更新列表和系统,并提供了相应的命令。 ... [详细]
  • 本文介绍了在Cpp中将字符串形式的数值转换为int或float等数值类型的方法,主要使用了strtol、strtod和strtoul函数。这些函数可以将以null结尾的字符串转换为long int、double或unsigned long类型的数值,且支持任意进制的字符串转换。相比之下,atoi函数只能转换十进制数值且没有错误返回。 ... [详细]
  • 本文介绍了解决java开源项目apache commons email简单使用报错的方法,包括使用正确的JAR包和正确的代码配置,以及相关参数的设置。详细介绍了如何使用apache commons email发送邮件。 ... [详细]
  • 进入配置文件目录:[rootlinuxidcresin-4.0.]#cdusrlocalresinconf查看都有哪些配置文件:[rootlinuxid ... [详细]
  • PHP连接MySQL的2种方法小结以及防止乱码【PHP】
    后端开发|php教程PHP,MySQL,乱码后端开发-php教程PHP的MySQL配置报错信息:ClassmysqlinotfoundinAnswer:1.在confphp.ini ... [详细]
author-avatar
一个人的生活啦
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有