cen's blog cen's blog
首页
  • 编程文章

    • markdown使用
  • 学习笔记

    • 《JavaScript教程》
    • C++学习
    • C++数据结构
    • MySQL
    • Linux
  • 高中时代
  • 工作日常
  • CLion
  • 技术文档
  • GitHub技巧
  • Nodejs
  • 博客搭建
  • 分类
  • 标签
  • 归档
关于
GitHub (opens new window)

cen

十年饮冰,难凉热血
首页
  • 编程文章

    • markdown使用
  • 学习笔记

    • 《JavaScript教程》
    • C++学习
    • C++数据结构
    • MySQL
    • Linux
  • 高中时代
  • 工作日常
  • CLion
  • 技术文档
  • GitHub技巧
  • Nodejs
  • 博客搭建
  • 分类
  • 标签
  • 归档
关于
GitHub (opens new window)
  • Linux环境搭建
  • 基本指令
  • 权限
  • Linux基础开发工具
  • 进程概念
  • 进程控制
  • 基础IO流
    • open
    • write
    • read
    • close
    • fd
      • 分配规则
      • 重定向
      • dup2
    • FILE
      • FILE当中的文件描述符
      • FILE当中的缓冲区
    • 文件系统
    • 软硬链接
      • 软链接
      • 硬链接
    • 文件的时间
  • 动态库和静态库
  • 进程通信
  • 进程信号
  • 多线程
  • 线程安全
  • Linux
cen
2025-02-19
目录

基础IO流

# open

函数描述:

#include <fcntl.h>

int open(const char *pathname, int flags);
int open(const char *pathname, int flags, mode_t mode);
1
2
3
4

参数:

  • pathname:文件路径
  1. 以路径的方式给出,则当需要创建该文件时,就在pathname路径下进行创建
  2. 以文件名的方式给出,默认在当前路径下进行创建
  • flags:打开文件的方式
    • O_RDONLY:只读
    • O_WRONLY:只写
    • O_RDWR:读写
    • O_CREAT:如果文件不存在,就创建
    • O_TRUNC:如果文件存在,就将文件清空
    • O_APPEND:如果文件存在,就将文件指针指向文件末尾 注意:O_RDONLY、O_WRONLY和O_RDWR有且只能有一个,多个选项传入时,可以使用|运算符进行组合
  • mode:文件的权限,默认为0666

返回值:

  • 成功:返回文件描述符,Linux进程默认情况下会有3个缺省打开的文件描述符,分别就是标准输入0、标准输出1、标准错误2
  • 失败:返回-1

# write

函数描述:

#include <unistd.h>

ssize_t write(int fd, const void *buf, size_t count);
1
2
3

我们可以使用write函数,将buf位置开始向后count字节的数据写入文件描述符为fd的文件当中。

参数:

  • fd:文件描述符
  • buf:要写入的数据
  • count:要写入的数据长度

返回值:

  • 如果数据写入成功,实际写入数据的字节个数被返回
  • 如果数据写入失败,返回-1

# read

函数描述:

#include <unistd.h>

ssize_t read(int fd, void *buf, size_t count);
1
2
3

我们可以使用read函数,从文件描述符为fd的文件读取count字节的数据到buf位置当中。

参数:

  • fd:文件描述符
  • buf:要读取的数据
  • count:要读取的数据长度

返回值:

  • 如果数据读取成功,实际读取数据的字节个数被返回
  • 如果数据读取失败,返回-1

示例:

#include <fcntl.h>
#include <unistd.h> // 包含 close, lseek, read, write 的声明
#include <cstring>  // 包含 strlen 的声明

int main() {
    int fd = open("log.txt", O_RDWR | O_CREAT, 0666);
    if (fd == -1) {
        // 错误处理:无法打开或创建文件
        return 1;
    }
    const char* mes = "我们是冠军!";
    ssize_t wrsize = write(fd, mes, strlen(mes));
    if(wrsize == -1){
        // 错误处理:写入失败
        close(fd);
        return 1;
    }

    // 将文件位置指示器移到文件开头
    if(lseek(fd, 0, SEEK_SET) == -1){
        // 错误处理:定位失败
        close(fd);
        return 1;
    }
    char ch;
    while (1){
        ssize_t s = read(fd, &ch, 1);
        if (s <= 0){
            break;
        }
        write(1, &ch, 1); // 向文件描述符为1的文件写入数据,即向显示器写入数据
    }
    close(fd); // 关闭文件描述符
    return 0;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35

# close

函数描述:

int close(int fd);
1

使用close函数关闭文件

# fd

文件描述符是Linux系统对文件的一种抽象,它表示了一个打开的文件,每个文件描述符都对应一个打开的文件,并且每个文件描述符都是唯一的。 文件描述符在Linux系统下,一般以数字表示,从0开始,依次递增,从0到1023是系统保留的,从1024开始才是用户自定义的。 在Linux系统下,文件描述符的分配机制是动态的,即当程序打开一个文件时,系统会分配一个文件描述符给该文件,当程序关闭该文件时,系统会释放该文件描述符。

# 分配规则

进程创建的时候会默认打开0、1、2,0就是标准输入流,对应键盘;1就是标准输出流,对应显示器;2就是标准错误流,也是对应显示器。 文件描述符的分配规则:在files_struct数组当中,找到当前没有被使用的最小的一个下标,作为新的文件描述符。

int main() {
    close(0);
    int fd = open("a.txt", O_WRONLY | O_CREAT | O_TRUNC);
    cout << fd << endl;

    close(fd);  // 0 被关闭了,fd = 0
    return 0;
}
1
2
3
4
5
6
7
8

# 重定向

int main() {
    close(1);
    int fd = open("a.txt", O_RDWR | O_CREAT, 0666);
    fflush(stdout);
    cout << fd << endl;

    close(fd);
    return 0;
}
1
2
3
4
5
6
7
8
9

最终在我们发现,本来应该输出到显示器上的内容,输出到了文件a.txt当中,其中,fd=1。这种现象叫做输出重定向。

# dup2

要完成重定向我们只需进行文件指针数组当中元素的拷贝即可

#include <unistd.h>

int dup(int oldfd);
int dup2(int oldfd, int newfd);
1
2
3
4

函数描述: dup2:复制文件描述符,将oldfd复制到一个新的文件描述符,返回新的文件描述符,如果newfd已经存在,就将oldfd复制到newfd,并关闭newfd 参数:

  • oldfd:要复制的文件描述符
  • newfd:要复制到的文件描述符 返回值:
  • 成功:返回新的文件描述符
  • 失败:返回-1
int main() {
    int fd = open("a.txt", O_RDWR | O_CREAT, 0666);
    dup2(fd, 1);
    cout << fd << endl;
    close(fd);
    return 0;
}
1
2
3
4
5
6
7

最终在文件a.txt当中,输出了3

int main() {
    int fd = open("a.txt", O_RDWR | O_CREAT);
    dup2(fd, 0);
    char line[64];
    while (1) {
        if (fgets(line, sizeof(line), stdin) == NULL)
            break;
        cout << line << endl;

    }
    close(fd);
    return 0;
}
1
2
3
4
5
6
7
8
9
10
11
12
13

#

# FILE

# FILE当中的文件描述符

FILE实际上是struct _IO_FILE结构体的一个别名。

因为IO相关函数与系统调用接口对应,并且库函数封装系统调用,所以本质上,访问文件都是通过fd访 问的,结构体中_fileno就是fd

struct _IO_FILE {
	int _flags;       /* High-order word is _IO_MAGIC; rest is flags. */
#define _IO_file_flags _flags

	//缓冲区相关
	/* The following pointers correspond to the C++ streambuf protocol. */
	/* Note:  Tk uses the _IO_read_ptr and _IO_read_end fields directly. */
	char* _IO_read_ptr;   /* Current read pointer */
	char* _IO_read_end;   /* End of get area. */
	char* _IO_read_base;  /* Start of putback+get area. */
	char* _IO_write_base; /* Start of put area. */
	char* _IO_write_ptr;  /* Current put pointer. */
	char* _IO_write_end;  /* End of put area. */
	char* _IO_buf_base;   /* Start of reserve area. */
	char* _IO_buf_end;    /* End of reserve area. */
	/* The following fields are used to support backing up and undo. */
	char *_IO_save_base; /* Pointer to start of non-current get area. */
	char *_IO_backup_base;  /* Pointer to first valid character of backup area */
	char *_IO_save_end; /* Pointer to end of non-current get area. */

	struct _IO_marker *_markers;

	struct _IO_FILE *_chain;

	int _fileno; //封装的文件描述符
#if 0
	int _blksize;
#else
	int _flags2;
#endif
	_IO_off_t _old_offset; /* This used to be _offset but it's too small.  */

#define __HAVE_COLUMN /* temporary */
	/* 1+column number of pbase(); 0 is unknown. */
	unsigned short _cur_column;
	signed char _vtable_offset;
	char _shortbuf[1];

	/*  char* _save_gptr;  char* _save_egptr; */

	_IO_lock_t *_lock;
#ifdef _IO_USE_OLD_IO_FILE
};
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43

# FILE当中的缓冲区

示例:

#include <stdio.h>
#include <string.h>

int main() {
    const char *msg0 = "hello printf\n";
    const char *msg1 = "hello fwrite\n";
    const char *msg2 = "hello write\n";
    printf("%s", msg0);
    fwrite(msg1, strlen(msg0), 1, stdout);
    write(1, msg2, strlen(msg2));
    fork();
    return 0;
}
1
2
3
4
5
6
7
8
9
10
11
12
13

运行:

[cen@VM-4-9-opencloudos lesson01-基础IO]$ make
g++ test.cpp -o test
[cen@VM-4-9-opencloudos lesson01-基础IO]$ ./test
hello printf
hello fwrite
hello write
[cen@VM-4-9-opencloudos lesson01-基础IO]$ touch file;
[cen@VM-4-9-opencloudos lesson01-基础IO]$ ./test > file     # > 将命令输出写入到文件或设备中
[cen@VM-4-9-opencloudos lesson01-基础IO]$ cat file
hello write
hello printf
hello fwrite
hello printf
hello fwrite
1
2
3
4
5
6
7
8
9
10
11
12
13
14

原因:

printf fwrite库函数会自带缓冲区,而write系统调用没有带缓冲区。另外,我们这里所说的缓冲区, 都是用户级缓冲区。printf fwrite是库函数, write是系统调用,库函数在系统调用的“上层”, 是对系统 调用的“封装”,但是 write没有缓冲区,而 printf fwrite有,足以说明,该缓冲区是二次加上的,又因为是 C++,所以由C++标准库提供。

# 文件系统

磁盘文件由两部分构成,分别是文件内容和文件属性。文件内容就是文件当中存储的数据,文件属性就是文件的一些基本信息,文件属性又被称为元信息。

在Linux操作系统中,文件的元信息和内容是分离存储的,其中保存元信息的结构称之为inode,因为系统当中可能存在大量的文件,所以我们需要给每个文件的属性集起一个唯一的编号,即inode号。

[cen@VM-4-9-opencloudos lesson01-基础IO]$ ls -i
134218353 file  134218355 Makefile  134218309 test  134218352 test.cpp
1
2

# 软硬链接

# 软链接

命令:

ln -s file sort_file.link
1

# 硬链接

ln file hard_file.link
1

示例:

[cen@VM-4-9-opencloudos lesson01-基础IO]$ ll -li
total 32
134218353 -rw-r--r-- 1 cen cen    64 Mar 17 19:29 file
134218355 -rw-r--r-- 1 cen cen    71 Mar 12 10:28 Makefile
134218309 -rwxr-xr-x 1 cen cen 16816 Mar 17 19:25 test
134218352 -rw-r--r-- 1 cen cen   544 Mar 17 19:25 test.cpp
[cen@VM-4-9-opencloudos lesson01-基础IO]$ 
[cen@VM-4-9-opencloudos lesson01-基础IO]$ ln -s file sort_file.link
[cen@VM-4-9-opencloudos lesson01-基础IO]$ ll -li
total 32
134218353 -rw-r--r-- 1 cen cen    64 Mar 17 19:29 file
134218355 -rw-r--r-- 1 cen cen    71 Mar 12 10:28 Makefile
134218354 lrwxrwxrwx 1 cen cen     4 Mar 17 21:13 sort_file.link -> file
134218309 -rwxr-xr-x 1 cen cen 16816 Mar 17 19:25 test
134218352 -rw-r--r-- 1 cen cen   544 Mar 17 19:25 test.cpp
[cen@VM-4-9-opencloudos lesson01-基础IO]$ ln file hard_file.link
[cen@VM-4-9-opencloudos lesson01-基础IO]$ ll -li
total 36
134218353 -rw-r--r-- 2 cen cen    64 Mar 17 19:29 file
134218353 -rw-r--r-- 2 cen cen    64 Mar 17 19:29 hard_file.link
134218355 -rw-r--r-- 1 cen cen    71 Mar 12 10:28 Makefile
134218354 lrwxrwxrwx 1 cen cen     4 Mar 17 21:13 sort_file.link -> file
134218309 -rwxr-xr-x 1 cen cen 16816 Mar 17 19:25 test
134218352 -rw-r--r-- 1 cen cen   544 Mar 17 19:25 test.cpp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24

结论:

  1. 软链接文件相对于源文件来说是一个独立的文件,该文件有自己的inode号,但是该文件只包含了源文件的路径名
  2. 软链接文件只是其源文件的一个标记,当删除了源文件后,链接文件不能独立存在,虽然仍保留文件名,但却不能执行或是查看软链接的内容了
  3. 硬链接文件就是源文件的一个别名,一个文件有几个文件名,该文件的硬链接数就是几
  4. 当硬链接的源文件被删除后,硬链接文件仍能正常执行,只是文件的链接数减少了一个,因为此时该文件的文件名少了一个
  5. 软链接是一个独立的文件,有独立的inode,而硬链接没有独立的inode。
  6. 软链接相当于快捷方式,硬链接本质没有创建文件,只是建立了一个文件名和已有的inode的映射关系,并写入当前目录

# 文件的时间

文件的三个时间信息:

  • Access: 文件最后被访问的时间
  • Modify: 文件内容最后的修改时间
  • Change: 文件属性最后的修改时间
上次更新: 2025/05/04, 22:48:52
进程控制
动态库和静态库

← 进程控制 动态库和静态库→

最近更新
01
线程安全
05-21
02
cmake教程
05-08
03
项目
05-07
更多文章>
Theme by Vdoing | Copyright © 2024-2025 京ICP备2020044002号-3 京公网安备11010502056119号
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式