Linux系统编程手册 学习笔记(二)

第四章:文件I/O:通用的I/O模型

在该章节中讨论的文件描述符的概念。其中包括:打开文件,关闭文件,从文件中读取数据和向文件中写数据。

  • 概述
    所有执行I/O操作的系统调用都以文件描述符(一个非负整数(通常是比较小的整数))来指代打开的文件。文件描述符用以表示所有类型的已打开文件,包括管道(pipe)、FIFO、socket、终端、设备和普通文件。
    下面介绍执行I/O操作的4个主要系统调用:
  • open:fd=open(pathname,flags,mode)函数打开pathname所标识的文件,并返回文件描述符,用以在后续函数调用中指代打开的文件。如果文件不存在,open()函数可以创建之,这取决于对位掩码参数flags的设置。flags参数还可以指定文件的打开方式:只读、只写亦或读写方式。mode参数则指定了由open()调用创建文件的访问权限,如果open()函数没有创建文件,则可以忽略或省略mode参数。
  • read :numread=read(fd,buffer,count)调用从fd所指代的打开文件中读取至多count字节的数据,并存储到buffer中。read()调用的返回值为实际读取到的字节数。如果再无字节刻度,则返回值为。
  • write:numwritten=write(fd,buffer,count)调用从buffer中读取多达count字节的数据写入由fd所指代的已打开文件中。write()调用的返回值为实际写入文件中的字节数,且有可能小于count。
  • close:status=close(fd)在所有输入/输出操作完成后,调用close(),释放文件描述符fd与之相关的内核资源。

实现一个简版的cp(1)命令:

#include<sys/stat.h>
#include<fcntl.h>
#include "tlpi_hdr.h"
#include "error_functions.h"//用来输入错误信息
#ifndef BUF_SIZE
#define BUF_SIZE 1024
#endif

int main(int argc, char const *argv[])
{
    int inputFd,outputFd,openFlags;//用来存储I/O调用的返回值
    mode_t filePerms;  //整型,用来表示文件权限及类型  
    ssize_t numRead;  //有符号整型,当字节数(为负时)表示错误
    char buf[BUF_SIZE];

//strcmp():比较两个字符串设这两个字符串为str1,str2,若str1==str2,则返回零
    if(argc!=3||strcmp(argv[1],"--help")==0)
        usageErr("%s old-file new-file\n",argv[0]);

    inputFd=open(argv[1],O_RDONLY);
    if(inputFd==-1)
        errExit("opening file %s",argv[1]);

    openFlags=O_CREAT | O_WRONLY | O_TRUNC; //open()函数的参数,后面会介绍
    filePerms=S_IRUSR | S_IWUSR |S_IWGRP | S_IROTH |S_IWOTH;
    //文件类型参数,后面介绍
    outputFd=open(argv[2],openFlags,filePerms);
    if(outputFd==-1)
        errExit("opening file %s",argv[2]);

    while((numRead=read(inputFd,buf,BUF_SIZE))>0)
        if(write(outputFd,buf,numRead)!=numRead)
            fatal("couldn't write whole buffer");    
    if(numRead==-1)
        errExit("read");

    if(close(inputFd)==-1)
        errExit("close input");
    if(close(outputFd)==-1)
        errExit("close output");
    exit(EXIT_SUCCESS);
    return 0;
}
  • 通用I/O
    Unix I/O模型的显著特点之一就是输入/输出的通用性概念。
    就是说open、read、write、close可以对所有类型的文件执行I/O操作,包括终端设备。所以如果只使用了这些系统调用编写的程序,可以对系统内所有类型的文件使用。
    实现通用I/O的前提就是确保每一个文件系统和设备驱动程序都实现了相同的I/O系统调用集。因为linux下文件系统和设备所特有的操作细节已经放在内核中处理,所以在编程时通常可以忽略设备转悠的因素。而如果应用程序需要访问文件系统和设备的专有功能时,可以选择ioctl()系统调用来处理,该系统调用为I/O模型之外的专有特性提供了访问接口。
  • 打开一个文件:Open()
    open()调用既可以打开一个已经存在的文件,也能创建并打开一个新文件。
#include<sys/stat.h>
#include<fcntl.h>
int open(const char* pathname,int flags,.../*mode_t mode*/); 
//返回:打开成功的话返回文件描述符,打开失败的话返回-1 

要打开的文件由参数pathname来标识,如果pathname是一个符号链接,那么该调用会对其进行解引用。如果调用成功,open()返回文件描述符,用于在后续函数调用中指代该文件,如果发生错误,则返回-1,并将errno置为相应的错误标志。参数flags为位掩码,用来指定文件的访问模式。 当调用open()创建新文件时,位掩码参数mode指定了文件的访问权限。
如果open()并未指定O_CREAT标志,则可以省略mode参数。 O_RDONLY —->以只读方式打开文件 O_WRONLY —->以只写方式打开文件 O_RDWR —->以读写方式打开文件。
open函数使用的例子:

#include<sys/stat.h>
#include<fcntl.h>
#include "tlpi_hdr.h"
#include "error_functions.h"//用来输入错误信息

int main(int argc, char const *argv[])
{
    int fd;
    fd=open("startup",O_RDONLY);
    if(fd==-1)
        errExit("open");    
    fd=open("myfile",O_RDWR|O_CREAT|O_TRUNC,S_IRUSR|S_IWUSR);

    if(fd==-1)
        errExit("open");
    fd=open("w.log",O_WRONLY|O_CREAT|O_TRUNC|O_APPEND,S_IRUSR|S_IWUSR);
    if(fd==-1)
        errExit("open");
    return 0;
}

open()调用所返回的文件描述符数值:SUSv3规定,如果调用open()成功,必须保证其返回值为进程为用文件描述符中数值最小者。
所以可以利用这项特性来以特定的文件描述符打开某一个文件。
例如:下例代码会确保使用标准输入(文件描述符0)打开一个文件

#include<sys/stat.h>
#include<fcntl.h>
#include "tlpi_hdr.h"
#include "error_functions.h"//用来输入错误信息
int  main(int argc, char const *argv[])
{
    int fd;

    if(close(STDIN_FILENO)==-1)  
    //close()关闭STDIN_FILENO 使得系统内最小文件描述符为0
        errExit("close");    
    fd=open("startup",O_RDONLY);
    if(fd==-1)
        errExit("open");
    printf("%d\n",fd);
    return 0;
}

结果截图:
运行结果

由于文件描述符0未用,所以open()调用势必使用此描述符打开文件。

  • open()调用中的flags参数
    在上述的代码例子中,flags参数除了使用文件访问标志外,还使用了其他操作标志。如下图所示
    这里写图片描述
    这里写图片描述
    上述访问标志可以分为三组:
    1、文件访问模式标志:先前描述的O_RDONLY、O_WRONLY 、O_RDWR标志均在此类中,调用open()时,上述三者在flags参数中不能同时使用,只能指定其中一种。 调用fcntl()的F_GETFL操作可以检索文件的访问模式。
    2、文件创建标志:这些标志位于图中第二部分,其控制范围不拘于open()调用行为的方方面面,还涉及后续I/O操作的各个选项。这些操作不能被检索,也无法修改。
    3、已打开文件的状态标志:这些标志时图中的剩余部分。使用fcntl()的F_GETFL和F_SETFL操作可以分别检索和修改此类标志。有时干脆称之为文件状态标志。

flags常量的详解:
O_APPEND : 标志如其名,总是在文件尾部追加数据。
O_ASYNC : 当对open()调用所返回的文件描述符可以实施I/O操作时,系统会产生一个信号通知进程。这一个特性,也被称之为信号驱动I/O,仅对特定类型的文件有效,诸如终端、FIFO及socket。在linux中,调用open()时指定O_ASYNC标志没有任何实质效果,要启用信号驱动I/O特性,必须调用fcntl()的F_SETFL操作来设置O_ASYNC标志。
O_CLOEXEC : 为新(创建)的文件描述符启用close-on-flag标志(FD_CLOEXEC)。使用O_CLOEXEC标志(打开文件),可以免去程序执行fcntl()的F_GETFD和F_SETFD操作来设置close-on-exec标志的额外工作。———————-看不懂
O_CREATE : 如果文件不存在,将创建一个新的空文件。即使文件以只读方式打开,此标志依然有效。如果在open()调用中指定O_CREATE标志,那么还要提供mode参数,否则,会将新文件的权限设置为栈中的某个随机值。
O_TRUNC : 如果文件已经存在且为普通文件,那么将清空文件内容,将其长度置为0。在linux下使用此标志,无论以读、写方式打开文件,都可清空文件内容(在这两种情况下,都必须拥有对文件的读写权限)。

猜你喜欢

转载自blog.csdn.net/weixin_39116058/article/details/82502465