计算机系统基础-学习记录16-系统级I/O

本文深入介绍了Linux系统级I/O,包括文件的基本操作(打开、定位、读写、关闭),文件分类(常规文件、目录、socket),以及使用Unix I/O函数进行文件操作的实例。通过理解这些概念和函数,可以更好地掌握系统级I/O的工作原理和文件管理。
摘要由CSDN通过智能技术生成
系统级I/O

  I/O,即input/output,是指主存与外部设备之间的数据交互。在高级语言中使用的输入输出函数(例如,C语言中的printf和scanf),被归类为高级别的I/O(higher-level I/O)函数,而这些操作其实是由系统级的I/O(system-level I/O)函数组成的。理解了系统级I/O,便能更加容易地去理解高层I/O的工作原理

文件

  诸如网络、磁盘以及终端等一切I/O设备,均被称作“文件”

文件的基本操作

  对于文件而言,都具有以下的四个基本操作:

1、打开文件
  在打开文件时,内核会返回一个非负整数,称为“descriptor”。在此后的操作中,这个整数都将会用于表示这个被打开的文件
每一个由Linux Shell创建的进程,在初始时刻都会有三个被打开的文件,它们分别为:标准输入(standard input)、标准输出(standard output)、标准错误(standard error);这三个文件对应的descriptor值分别为0、1、2

2、改变当前文件位置
  在打开文件后,内核会存有一个标记“文件位置”的数值k,这个值在最开始时将会被设为0。文件位置k,表示相对于文件的起点,共有k个字节的偏移。k值是可以在应用程序中被改变的

3、读取和写入文件
  读取(read)操作会从文件的位置k处开始,复制n>0个字节到内存里,复制完后将会令k=k+n,以此类推。如果在某一时刻,k值已经大于了文件大小m,则会触发EOF(end-of-file)
写入(write)操作同理,只不过是从内存往文件中复制

4、关闭文件
  在关闭文件时,内核会释放文件所对应的数据结构,并同时释放此文件对应的descriptor,使得这个descriptor可以被之后打开的文件所使用
如果进程被关闭,则内核会关闭所有打开的文件,并将相应的资源释放出来

文件分类

  对于Linux系统而言,文件分为三种类型:

1、常规文件
  包含任意的数据,例如二进制文件、ASCII文件以及Unicode文件

2、目录
  以列表形式呈现,其中元素的类型为链接,每一个链接都与一个文件名相对应(文件名可以是另一个目录)
每一个目录至少会包含两个链接:本级目录(.)或上级目录(…)

3、socket
  用于通过网络,和其他进程进行交流

文件操作

使用Unix I/O提供的函数

打开/关闭文件

  Unix I/O提供了打开和关闭文件的方法。其中,打开文件的对应函数为:

int open(char *filename, int flags, mode_t mode);

  其中,参数filename为文件名,flags表示文件打开形式,mode则表示用户权限

  对于flags,有只读、只写和读写,以及强制创建新文件、覆盖原有文件和追加到原有文件尾部这六种选项

  而对于mode,则共有九个模式。mode包括了拥有者、用户组和普通用户这三种分类以及对应的读、写和执行这三种权限:

  拥有者-读|拥有者-写|拥有者-执行|用户组-读|用户组-写|用户组-执行|普通用户-读|普通用户-写|普通用户-执行|

  在调用open函数,按提供的选项去打开文件后,就会返回这个文件所对应的descriptor值。而如果文件打开失败,则会返回-1

  关闭文件则是使用close函数:

int close(int fd);

  参数fd即要关闭的那个文件,所对应的descriptor值。调用此函数后,则会将descriptor为fd所对应的那个文件关闭

  下述代码使用了这两个函数,以只读的方式创建了一个拥有者和用户组均可读写执行、普通用户可读的,名为“file2.txt”的文件:

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

int main(){
    char *name = "file2.txt";
    int flags = O_WRONLY|O_CREAT|O_TRUNC;
    mode_t mode = S_IRUSR|S_IWUSR|S_IXUSR|S_IRGRP|S_IWGRP|S_IXGRP|S_IROTH;
    int fd = open(name, flags, mode);
    printf("fd=%d\n", fd);
    close(fd);
    return 0;
}

  运行后,命令行输出:

fd=3

  这正是在原有的三个文件(标准输入、标准输出、标准错误)后,打开的第一个文件,所以descriptor值为3

  再使用ls -l,查看文件信息,即可发现权限也相应地被设置好了:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-k9tk70FL-1618325840468)(C:\Users\蔡三圈\AppData\Roaming\Typora\typora-user-images\image-20210412221649643.png)]

读取/写入文件

  在打开文件后,便可以进行读取和写入的操作。关于这两种操作,可以使用read和write函数:

ssize_t read(int fd, void *buf, size_t n);
ssize_t write(int fd, const void *buf, size_t n);

  其中,fd是打开的文件对应的descriptor值,buf是用于读取和写入的缓冲地址,n是能够复制的最大字节数

  下述main函数从file.txt文件里读取字符,并写入到了file2.txt文件中:

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

int main(){
    int fd1, fd2;
    fd1 = open("file.txt", O_RDONLY, 0);
    fd2 = open("file2.txt", O_WRONLY|O_CREAT|O_TRUNC, 0x1fd);
    char *c;
    read(fd1, c, 20);
    write(fd2, c, 20);
    close(fd1);
    close(fd2);
    return 0;
}

读取数据信息

  对于一个文件,可以使用stat和fstat函数查看打开的文件的相关信息:

int stat(const char *filename, struct stat *buf);
int fstat(int fd, struct stat *buf);

  其中,stat接受的输入为文件名,而fstat接受的输入为descriptor。而参数buf则是用于存储文件信息的变量,其类型stat具有如下结构:

struct stat{
    dev_t st_dev;
    ino_t st_ino;
    mode_t st_mode; /*可以使用sys/stat.h中的宏来判断是否为某一类文件(常规文件?目录?socket?)*/
    nlink_t st_nlink;
    uid_t st_uid; /*拥有者的用户id*/
    gid_t st_gid; /*用户组id*/
    dev_t st_rdev;
    off_t st_size; /*总字节大小*/
    unsigned long st_blksize;
    unsigned long st_blocks;
    time_t st_atime; /*最后访问时间*/
    time_t st_mtime; /*最后修改(modification)时间*/
    time_t st_ctime; /*最后更改(change)时间*/
};

  使用这些函数,就可以获取文件的信息了:

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

int main(){
    int fd = open("file.txt", O_RDONLY, 0);
    struct stat stat;
    fstat(fd, &stat);
    printf("Is this a regular file? %d\n", S_ISREG(stat.st_mode));
    printf("Is this a directory file? %d\n", S_ISDIR(stat.st_mode));
    printf("Is this a network socket? %d\n", S_ISSOCK(stat.st_mode));
    printf("user id: %d\n", stat.st_uid);
    printf("user group id: %d\n", stat.st_gid);
    printf("size: %d bytes\n", stat.st_size);
    return 0;
}

  输出结果为:

Is this a regular file? 1
Is this a directory file? 0
Is this a network socket? 0
user id: 1035
user group id: 1036
size: 14 bytes

读取目录

  在读取目录时,可以使用opendir函数:

DIR *opendir(const char *name);

  返回DIR类型,将DIR类型的变量放入到readdir函数:

struct dirent *readdir(DIR *drip);

  会返回下一个文件的dirent类型的结构,该结构定义如下:

struct dirent{
    ino_t d_ino;
    char d_name[256]; /*文件名*/
};

  每调用一次readdir,都会从变量drip中选出目录中的下一个文件。而如果要关闭这个打开的目录,则需要使用closedir函数:

int closedir(DIR *drip);

  下述代码从当前目录中读取并打印了所有文件的文件名:

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

int main(){
    DIR *dirp = opendir(".");
    struct dirent *dirent;
    dirent = readdir(dirp);
    while(dirent != NULL){
        printf("file name: %s\n", dirent->d_name);
        dirent = readdir(dirp); // next file
    }
    return 0;
}

  执行结果:

file name: .
file name: ..
file name: ch10_3.c
file name: a.out
file name: ch10_1.c
file name: file.txt
file name: dir.c
file name: statStruct.c
file name: ch10_2.c
file name: ch10_4.c
file name: file2.txt
file name: a.asm

文件共享

  对于每一个打开的文件,内核都会用三个相关联的数据结构来表示,它们分别是:descriptor表、文件表和v-node表

  descriptor表的每个下标都对应了打开的文件descriptor值,此表下标所对应的那个元素,会指向该descriptor值相对应的文件表。每个进程的descriptor表都是独立的

  文件表是每个进程共享的。在文件表中,有一个refcnt值,会记录指向该文件表的指针总数。此外还有一个变量,用于记录当前的文件位置(在文件中的位置)。同时,文件表也有指向v-node表的指针

  v-node表也是每个进程共享的。在v-node表中,包含了大多数前面提到的stat结构里的元素

由此可见,在一个进程中,即便是两个descriptor值不同的文件,也可以最终指向同一个v-node表,即实现了文件共享

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-8q2ri3Rq-1618325840470)(C:\Users\蔡三圈\AppData\Roaming\Typora\typora-user-images\image-20210413223631938.png)]

I/O重定义

  使用dup2函数,可以改变I/O的文件对象:

int dup2(int oldfd, int newfd);

  此函数的作用是将oldfd的指向,复制给newfd,使得descriptor表中的newfd指向和oldfd的指向一致

  在Linux指令中也有重定义I/O的操作,例如:

ls > foo.txt

  便是将使用ls命令列出的信息,写入到文件foo.txt中(而不是默认的标准输出)

标准I/O

  C语言提供了标准I/O库,这个库是高层次的I/O,可以用于替代底层的Unix I/O

  标准I/O库将文件视作“流”(stream),在程序开始运行的时候,会有标准输入流、标准输出流和标准错误流

  标准I/O库中的函数,便是平常使用C语言时经常用到的函数:printf、scanf、fread、fwrite、fopen、fclose等

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值