首页 > 代码库 > TLPI(liunx/unix系统编程手册)笔记(三) 文件IO:通用的IO模型

TLPI(liunx/unix系统编程手册)笔记(三) 文件IO:通用的IO模型

 读下来总的就是介绍了四个IO的API--open,read,write,close。

大家都是知道的,everything is file,在linux系统里面一切都是看作文件来操作的,学习linux就得先学好文件IO,也可以看见TLPI这本书的介绍完一些概念之后就开始介绍文件IO了。

IO,大概的分为磁盘文件IO,buffering(缓冲)IO。貌似缓冲的水很深,之后会写博客。

————————————————————————————————————

(1)文件描述符。

  在进行IO操作之前,总要先打开吧,open函数打开文件并返回一个非负整数,这个数就说我们所说的文件描述符。在shell中0,1,2已经被占领,而且这三个描述符总是打开的,书中提到最好是把0,1,2这种描述放大改了,遵循POSIX,0---STDIN_FILENO,1---STDOUT_FILENO,2---STDERR_FILENO。但是freopen会使这三个标准变化,就是说不一定是012了。

(2)函数。

  <1>.open

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

       int open(const char *pathname, int flags);
       int open(const char *pathname, int flags, mode_t mode);

       int creat(const char *pathname, mode_t mode);

  pathname就是路径,如果是符号链接的话会自动分解。

  flags是访问模式,为位掩码,但是这里值得注意的是,O_RDONLY | O_WRONLY 和 O_RDWR 是不一样的,原因是这三个宏是用012代替的,或运算的话逻辑上就会有错误。还有一些常用的O_APPEND文件尾追加数据,O_CREAT不存在时候创建,O_TRUNC清空文件。

  mode就是访问权限。下面是umask表可以用 | 。(例如S_IRUSR,R--read,USR用户组,很好记忆,记不得的时候就man)

              S_IRWXU  00700 user (file owner) has read, write and execute permission

              S_IRUSR  00400 user has read permission

              S_IWUSR  00200 user has write permission

              S_IXUSR  00100 user has execute permission

              S_IRWXG  00070 group has read, write and execute permission

              S_IRGRP  00040 group has read permission

              S_IWGRP  00020 group has write permission

              S_IXGRP  00010 group has execute permission

              S_IRWXO  00007 others have read, write and execute permission

              S_IROTH  00004 others have read permission

              S_IWOTH  00002 others have write permission

              S_IXOTH  00001 others have execute permission

  <2>read.

SYNOPSIS
       #include <unistd.h>

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

    ssize_t (signed size type),当然fd可以是STDIN_FILENO,这样就可以类似于gets()函数。

  <3>write用法和read差不多。

  <4>close. 

    close的错误检查是一定要有的。关闭一个未打开的文件描述符和重复关闭同一个文件描述符都是有可能的。书上指出可能会在NFS文件系统中出现。

  <5>lseek.   

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

       off_t lseek(int fd, off_t offset, int whence);

   

    The lseek() function repositions the offset of the open file associated with the file descriptor fd to the argument offset according to thedirective whence as follows:

  SEEK_SET
  The offset is set to offset bytes.开始的地方

  SEEK_CUR
  The offset is set to its current location plus offset bytes.当下位置

  SEEK_END
  The offset is set to the size of the file plus offset bytes.末尾

    offset是从文件开始计算的。

 文件空洞:

  就是lseek在可以在末尾之后添加字符,理应以0字符作为填充,但是和0字符填充的文件不一样,有文件空洞的文件在磁盘占有上面和没有空洞的是一样的,但是文件大小是会小,并不是以0字符填充的。网上查阅了相关资料,文件空洞是给内核说占有磁盘空间的。

  为了验证自己的想法,做了一些测试:

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

#define OFFSET 4096*1000
int main()
{
    int openFd_test1;
    int openFd_test2;
    off_t offset;
    char buf[] = "flie_hole";
    int lenthOfBuf = sizeof(buf);
    char buf_zero[OFFSET]={0};

    mode_t modePerms = S_IWUSR | S_IRUSR | S_IWGRP | S_IRGRP | S_IWOTH | S_IROTH;//rw-rw-rw-
    /*create two files for test*/
    openFd_test1 = open("test1", O_RDWR | O_CREAT | O_TRUNC, modePerms);
    openFd_test2 = open("test2", O_RDWR | O_CREAT | O_TRUNC, modePerms);
    if(openFd_test1 == -1 || openFd_test2 == -1)
        errExit("open");
    /*test1    --have file hole*/
    write(openFd_test1,buf,lenthOfBuf);
    offset = lseek(openFd_test1, OFFSET,SEEK_END);
    write(openFd_test1,buf,lenthOfBuf);
    /*test2    --haven‘t file hole*/
    write(openFd_test2,buf,lenthOfBuf);
    write(openFd_test2,buf_zero,OFFSET);
    write(openFd_test2,buf,lenthOfBuf);
    /*close two fd*/
    if(close(openFd_test1) == -1)
        errExit("close");
    if(close(openFd_test2) == -1)
        errExit("close");
    return 0;
}

  创建两个文件,test1用文件空洞,作为对比test2用0填充。我的电脑上的块大小是4096B

-rw-rw-r-- 1 aaron aaron 4096020 May 13 11:52 test1
-rw-rw-r-- 1 aaron aaron 4096020 May 13 11:52 test2

 

aaron@ubuntu:~/Documents/TLPI$ du -k test*
8 test1
4004 test2

可以看出两个文件的占有磁盘的大小是一样的,都是占有了4000M+20B,但是从真正的大小上面看,一个有8B,另外的是4004B。

好像是不对,应该是4008,原因是空洞边界的数据正好落在的快内。

外附上一段书上代码,寻找位置读写。

#include"tlpi_hdr.h"
#include<sys/stat.h>
#include<fcntl.h>
#include<ctype.h>

int
main(int argc,char *argv[])
{
    size_t len;
    off_t offset;
    int fd, ap, j;
    char *buf;
    ssize_t numRead, numWritten;
    
    if(argc < 3 || strcmp(argv[1],"--help") == 0)
        usageErr("%s file {r<length>|R<length>|w<string>|s<offset>}...\n",argv[0]);

    /*open file in read and write\ wr-wr-wr*/
    fd = open(argv[1],O_RDWR | O_CREAT, S_IRUSR | S_IWUSR | 
                                        S_IRGRP | S_IWGRP |
                                        S_IWOTH | S_IWOTH);
    if(fd == -1)
        errExit("open");
    
    for(ap = 2; ap < argc; ap++)
    {
        switch(argv[ap][0])
        {
            case ‘r‘:/*display bytes at current offset as text*/
            case ‘R‘:/*display bytes at current offset as hex*/
                len = getLong(&argv[ap][1],GN_ANY_BASE, argv[ap]);
                buf = malloc(len);
                if(buf =NULL)
                    errExit("malloc");
                
                numRead = read(fd, buf, len);
                if(numRead == -1)
                    errExit("read");
                if(numRead == 0)
                    printf("%s:end-of-file\n",argv[ap]);
                else
                {
                    printf("%s: ",argv[ap]);
                    for(j = 0; j < numRead ; j++)
                    {
                        if(argv[ap][0] == ‘r‘)
                            printf("%c", isprint((unsigned char)buf[j]?buf[j]:‘?‘));
                        else
                            printf("%02x",(unsigned int)buf[j]);
                    }
                }
            free(buf);
            break;

            case ‘w‘ : /*write*/
                numWritten = write(fd, &argv[ap][1], strlen(&argv[ap][1]));
                if(numWritten == -1)
                    errExit("write");
                printf("%s wrote %ld bytes \n",argv[ap],(long)numWritten);
                break;

            case ‘s‘:/*change file offset*/
                offset = getLong(&argv[ap][1],GN_ANY_BASE, argv[ap]);
                if(lseek(fd, offset, SEEK_SET) == -1)
                {
                    errExit("lseek");
                }
                printf("%s: seek succeeded\n",argv[ap]);
                break;

            default:
                cmdLineErr("Argument must start with [rRsw]:%s\n",argv[ap]);
        }
    }
    exit(EXIT_SUCCESS);
}

  getLong()函数是自己的定义的,在之前就有描述。

file {r<length>|R<length>|w<string>|s<offset>}(具体用法)
通过argv参数数组,提取数组第一字符作为选项,执行改选项需要的程序。

TLPI(liunx/unix系统编程手册)笔记(三) 文件IO:通用的IO模型