Unix系统级I/O

在Unix系统中,一且皆为文件。一个Linux文件就是一个字符序列,并且所有的I/O设备都被模型化成了文件。而所有的输入输出都被当作对对应文件的读和写。Linux提供了一组简单、低级的接口,使得所有的输入输出都可以用一种简单通用的方式来执行。

Linux文件的分类

每一个文件都有一个类型(type)来表示它在系统中的角色,主要有以下几种:

  • 普通文件。普通文件包括文本文件和二进制文件。
  • 目录。目录包含一组指向其目录内的连接(link)
  • 套接字文件。其主要用来和另外的进程进行跨网络通信。
  • 管道。管道包括匿名管道和命名管道。用来进行进程间的通信。
  • 符号链接。
  • 字符和块设备等。

文件的打开与关闭

进程通过open函数打开或创建一个新文件:

#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>

int open(char *filename, int flags, mode_t mode);
/* 返回:若成功返回文件的描述符,若出错返回-1 */

这里的filename可能有些误解,不单单是指文件名,它还可以是相对或绝对路径名。

open函数将filename转换为一个文件描述符,返回的描述符总是在当前进程中没有打开的最小描述符

关于文件描述符是如何分配的呢?在Linux中,每一个进程都会维护一个文件描述符表,这个表实现了一个整数到文件的映射关系。在描述符表中,0-2分别对应stdin、stdout和stderr文件。当在进程中要打开一个文件的时候,文件描述符会从3开始分配,然后是4、5...

flags指明了进程如何访问这些文件,具体如下:

掩码 描述
O_RDONLY 只读
O_WRONLY 只写
O_RDWR 可读可写
O_CREAT 如果文件不存在,就创建它的一个截断的空文件
O_TRUNC 如果文件已存在,就截断它
O_APPEND 在每次读写操作前,设置文件位置到文件的结尾处

上面这些掩码还可以叠加,例如:

fd = open("foo.txt", O_WRONLY|O_APPEND, 0);

mode参数指明了如果要创建新文件时,新文件的访问权限位。如果只是普通的读写文件,mode一般为0。具体规则如下:

掩码 描述
S_IRUSR 使用者(拥有者)能够读这个文件
S_IWUSR 使用者(拥有者)能够写这个文件
S_IXUSR 使用者(拥有者)能够执行这个文件
S_IRGRP 拥有者所在组的成员可以读这个文件
S_IWGRP 拥有者所在组的成员可以写这个文件
S_IXGRP 拥有者所在组的成员可以执行这个文件
S_IROTH 其他人(任何人)可以读这个文件
S_IWOTH 其他人(任何人)可以写这个文件
S_IXOTH 其他人(任何人)可以执行这个文件

进程通过close函数来关闭一个已经打开的文件。关闭一个已关闭的描述符会出错。

#include <unistd.h>

int close(int fd);
/* 返回:若成功返回0,出错返回-1 */

文件的读写

进程通过readwrite函数来对文件进行读写操作:

#include <unistd.h>

ssize_t read(int fd, void *buf, size_t n);
/* 返回:若成功返回读的字节数,若EOF则为0,若出错则为-1 */

ssize_t write(int fd, void *buf, size_t n);
/* 返回:若成功返回写的字节数,若出错则为-1 */

例如将从标准输入文件读,写到标准输出文件:

#include <stdio.h>
#include <unistd.h>

void main() {
        char c;
        while(read(STDIN_FILENO, &c, 1) != 0)
        write(STDOUT_FILENO, &c, 1);
}

需要说明一下的是,上面两个函数中,ssize_t的类型为long,而size_t的类型是unsigned long

读写文件的元数据

应用程序可以通过statfstat函数读取每个文件的详细信息(也叫做文件的元数据)

#include <unistd.h>
#include <sys/stat.h>

int stat(const char *filename, struct stat *buf);
int fstat(int fd, struct stat *buf);
/* 返回:若成功返回0,出错返回-1 */

上面两个函数都通过填写用来描述文件信息的stat数据结构来获取文件的详细信息。

struct stat {
     dev_t              st_dev;             // 文件所在设备ID
     ino_t              st_ino;             // inode编号
     mode_t             st_mode;            // 保护模式和文件类型
     nlink_t            st_nlink;           // 硬链接个数
     uid_t              st_uid;             // 所有者用户ID
     gid_t              st_gid;             // 所有者组ID
     dev_t              st_rdev;            // 设备ID(如果是特殊文件)
     off_t              st_size;            // 总体尺寸,以字节为单位
     unsigned long      st_blksize;         // 文件系统 I/O 块大小
     unsigned long      st_blocks;          // 已分配块个数
     time_t             st_atime;           // 上次访问时间
     time_t             st_mtime;           // 上次更新时间
     time_t             st_ctime;           // 上次状态更改时间
};

Linux建议我们使用在sys/stat.h中定义的宏谓词来确定st_mode成员的文件类型,例如:

  • S_ISREG(m):这是一个普通文件吗?
  • S_ISDIR(m):这是一个目录文件吗?
  • S_ISSOCK(m):这是一个网络套接字文件吗?

其他宏谓词就不再赘述。

读取目录内容

应用程序可以通过readdir系列函数来读取文件的内容:

#include <sys/types.h>
#include <dirent.h>

DIR *opendir(const char *name);
/* 返回:若成功,返回处理的指针,否则,返回NULL */

opendir返回指向目录流的指针。流是对条目有序列表的抽象,这里是指目录项的列表。

可以使用readdir函数来读取目录流中的内容。

#include <dirent.h>

struct dirent *readdir(DIR *dirp);
/* 返回:若成功,则返回下一个目录项的指针;若没有更多目录或出错,者为NULL */

每个目录项都是一个结构,其形式如下:

struct dirent {
        ino_t  d_ino;           /* inode */
        char   d_name[256];     /* 文件名 */
        /* 虽然在某些Linux系统中还包括其他成员,但上面两个成员对所有Unix系统都是通用的 */
};

最后,使用closedir来关闭流并释放所有资源:

#include <dirent.h>

int closedir(DIR *dirp);
/* 返回:若成功为0,错误为-1 */

下面给出一个读取指定目录文件的例子:

#include <stdio.h>
#include <sys/types.h>
#include <dirent.h>

void main(int argc, char **argv) {
    DIR *streamp;
    struct dirent *dep;

    streamp = opendir(argv[1]);
    while((dep = readdir(streamp)) != NULL) {
        printf("%s\n", dep->d_name);
    }
    closedir(streamp);
}

I/O重定向

这里稍稍提及一下I/O重定向。上文在介绍文件的打开与关闭的时候提到,每一个进程都会维护一个文件描述符表,这个描述符表的表项由进程打开的文件描述符来索引,描述符表项实现了描述符到真实文件的映射关系。但是,如果我们改变这种映射关系,这便是文件重定向。

文件重定向可以使用dup2函数:

#include <unistd.h>

int dup2(int oldfd, int newfd);
/* 返回:若成功返回非负的描述符,若出错返回-1 */

dup2实现了将newfd重定向到oldfd,这其中实际上是使用oldfd的表项去覆盖newfd表项以前的内容,如果newfd已经打开,则先关闭newfd再进行覆盖。并且,如果newfd所指向的文件的引用计数变为0,则会释放相应的资源(包括打开文件表、v-node表中的表项)。例如,可以将标准输出重定向到一个文件:

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

void main() {
    int fd;
    char c;

    fd = open("foo.txt", O_WRONLY, 0);
    dup2(fd, 1);
    while(scanf("%c", &c) != 0) {
        write(1, &c, 1);
    }
    close(fd);
    exit(0);
}

标准I/O

C语言中提供了标准的I/O库,里面定义了一组高级的输入输出函数。例如:

  • 打开和关闭文件的fopenfclose
  • 读和写字节的freadfwrite
  • 读和写字符串的fgetsfputs
  • 从流中格式化读取和写入的fscanffprintf
  • 以及printfscanffprintf等等。

标准I/O库将打开的文件模型化成一个流,一个流是一个指向FILE结构体的指针。另外,每一个ASCI C程序在开始时都打开三个流stdinstdoutstderr

FILE流是对文件描述符和流缓冲区的抽象,因为缓冲区的存在,提高了读取文件的性能。但是标准I/O也不是完美的,也正是由于某些缓冲区的存在,使得标准I/O在网络I/O方面出现短板。

小结

以上,主要讨论了如下几个I/O相关的接口:

  • 用于打开和关闭文件的openclose
  • 用于读取和写入文件的readwrite
  • 用于读取文件元数据的statfstat,以及判断文件类型的宏谓词
  • 用于读取目录的opendirreaddirclosedir
  • C语言提供给我们的更加高级别的I/O接口


Unix系统级I/O

原文地址:https://www.cnblogs.com/tcctw/p/11701392.html

时间: 2024-11-13 17:36:18

Unix系统级I/O的相关文章

系统级I/O学习记录

重要知识点 输入/输出(I/O) I/O是主存和外部设备(如磁盘驱动器.终端和网络)之间拷贝数据的过程. 输入操作是从I/O设备拷贝数据到主存. 输出操作是从主存拷贝到I/O设备. Unix I/O 在unix中所有的I/O设备都被模型化为文件,这使得所有的输入输出都能以一种统一的方式进行. 打开文件:一个应用程序通过要求内核打开相应的文件,来宣告它想要访问一个I/O设备.内核返回一个小的非负整数,叫做描述符,它在后续对此文件的所有操作中标识这个文件. Unix外壳创建的每个进程开始时都有三个打

Go推出的主要目的之一就是G内部大东西太多了,系统级开发巨型项目非常痛苦,Go定位取代C++,Go以简单取胜(KISS)

以前为了做compiler,研读+实现了几乎所有种类的语言.现在看语法手册几乎很快就可以理解整个语言的内容.后来我对比了一下go和rust,发现go的类型系统简直就是拼凑的.这会导致跟C语言一样,需要高超的技巧才能写大程序.而rust则没有这种问题,每个部分的组成都很和谐.---------------------------------------------------------------------- Rust是挺优秀的,函数式本来就比命令式来的优雅.但同时也要看到,两种语言的定位不同

第十章 系统级I/O

第十章 系统级I/O 一.Unix I/O 1.一个unix文件就是一个m个字节的序列 2.unix外壳创建的每个进程开始时都有三个打开的文件:标准输入(0) .标准输出(1)和标准错误(-1). 二.打开个关闭文件 1.int open(char *filename, int flags, mode_t mede );       //将filename转换为一个文件描述符,并且返回描述数字 filename 文件名 flag 如何访问或更多位掩码 mode 指定新文件的访问权限 2.clos

第10章 系统级I/O

第10章 系统级I/O 10.1 Unix I/O 一个Unix文件就是一个m个字节的序列:B0,B1,…,BK,…,Bm-1 Unix I/O:一种将设备优雅地映射为文件的方式,允许Unix内核引出一个简单.低级的应用接口,这使得所有的输入输出都能以一种统一且一致的方式来执行: 打开文件: 内核返回一个小的非负整数,叫做描述符. Unix外壳创建的每个进程开始时都有三个打开的文件:标准输入(描述符为0).标准输出(描述符为1).标准错误(描述符为2). 改变当前文件位置: 对于每个打开的文件,

[CSAPP笔记][第十章 系统级I/O]

第十章 系统级I/O 输入/输出(I/O) : 是指主存和外部设备(如磁盘,终端,网络)之间拷贝数据过程. 高级别I/O函数 scanf和printf <<和>> 使用系统级I/O函数实现 系统级I/O函数. Q:大多数时候高级别I/O函数都运行良好,为什么我们还要学Unix I/O A: 了解Unix I/O将帮助你理解其他的系统概念. 要深入理解其他概念,必须理解I/O. 有时你除了使用Unix I/O别无选择 标准I/O库没有提供读取文件元数据的方式. 如文件大小或文件创建时

Unix系统编程()文件描述符和打开文件之间的关系

目前学习到的是一个文件描述符对应着一个打开的文件,似乎是一一对应的关系.但是实际上并不是这样的.多个文件描述符指向同一个打开的文件,是可能的也是必要的.这些文件描述符可以在相同或者不同的进程中打开. 要理解具体情况,需要查看内核维护的3个数据结构. 进程级的文件描述符表 系统级的打开文件表 文件系统的i-node表 针对每个进程,内核为其维护打开文件的描述符(open file descriptor)表.该表的每一条目都记录了单个文件描述符的相关信息.包括有一下信息: 控制文件描述符操作的一组标

Android系统级技巧合集

Android系统级技巧合集(随时更新) #转载请注明来源# 1.高通骁龙系列查看CPU体质等级 CPU体质,即为CPU在工作频率下的电压.同一批次的CPU体质各有不同,体质越高,代表该颗CPU可在更高的频率下稳定工作,且在相同频率下工作时功耗相比同批次体质差的CPU要控制得更好. 以搭载高通骁龙801的小米4(OS:第三方原生Android)为例,可在/sys下的文件中查找到描述该CPU体质的文件. 路径为:/sys/module/clock_krait_8974/parameters/ 在该

在Linux/Unix系统下用iconv命令处理文本文件中文乱码问题

iconv命令是运行于linux/unix平台的文件编码装换工具.当我们在linux/unix系统shell查看文本文件时,常常会发现文件的中文是乱码的,这是由于文本文件的编码与当前操作系统设置的编码不同而引起的,这时可以使用iconv进行编码转换,从而解决乱码问题. 解决文本文件乱码问题分3步:1.确定文件编码,2.确定iconv是否支持此编码的转换,3.确定Linux/Unix操作系统编码,4.转换文件编码为与系统编码一致:下面通过对test.txt文件来举例. 1. 使用file命令来确定

Unix 系统下的 Nginx 1.4.x

Unix 系统下的 Nginx 1.4.x 本文档包括使用 PHP-FPM 为 Nginx 1.4.x HTTP 服务器安装和配置 PHP 的说明和提示. 本指南假定您已经从源代码成功构建 Nginx,并且其二进制文件和配置文件都位于 /usr/local/nginx. 如果您使用其他方式获取的 Nginx,请参考» Nginx Wiki 并对照本文档完成安装. 本文档仅包含 Nginx 服务器的基本配置,它将通过 80 端口提供 PHP 应用的处理能力. 如果您需要超出本文档范围的安装配置指导