Linux操作系统-文件(2)

Linux操作系统—文件(2)(2015-8-17)

分类:Linux操作系统

二:底层文件访问

文件描述符

  文件描述符是一个非负整数。对内核而言,所有打开的文件都由文件描述符引用。
  当打开一个现存文件或者创建一个新的文件时,内核向进程返回一个文件描述符。当读,写一个文件时,用open或者create返回的文件描述符标识该文件,将其作为参数传递给read或者write。从内核源码的角度来看,文件描述符其实是当前进程所打开的文件结构数组的下标。
  按照惯例,UNIX/Linux Shell使文件描述符0与进程的标准输入相结合,文件描述符1与标准输出相结合,文件描述符2与标准出错相结合。在头文件< unistd.h> 中定义了常量STDIN_FILENO,STDOUT_FILENO和STDERR_FILENO,其值分别为0,1,2。
  一个进程能打开的文件数由< limits.h >文件中的OPEN_MAX限定。

文件的创建,打开

  调用open函数可以打开或创建一个文件,其原型如下:

#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);

疑惑:这是什么?函数重载么?可函数重载不是C++里面的东西吗?

  打开一个文件后,即在文件描述符和文件之间建立了一个关联。open函数既可以打开一个已近存在的文件,也可以创建一个新的文件。具体执行打开操作还是创建操作由flag参数指定。open函数各参数和返回值的含义如下:
1. pathname:要打开或创建的文件的名字
2. flags:由下列一个或多个常数进行或运算构成

- O_RDONLY:只读打开
- O_WRONLY:只写打开
- O_RDWR:读,写打开
- APPEND:每次写时追加到文件的尾端
- O_CREAT:若此文件不存在则创建它,应结合第三个参数mode使用
- O_EXCL:结合O_CREATE,当文件不存在时,才创建文件
- O_TRUNC:如果此文件存在,而且为只读或只写则将其长度截断为0
  1. mode:存取许可权位,一个32位无符号整数,仅当创建新文件时才使用,由下列一个或多个常数进行或运算构成。应注意,最终文件权限受系统变量umask限制,是所设权限和umask的二进制“非”进行二进制“与”所得的结果。

    • S_IRUSR:文件所有者读
    • S_IWUSR:文件所有者写
    • S_IXUSR:文件所有者执行
    • S_IRGRP:用户组读
    • S_IWGRP:用户组写
    • S_IXGRP:用户组执行
    • S_IROTH:其它用户读
    • S_IWOTH:其它用户写
    • S_IXOTH:其它用户执行
  2. 返回值

    • 成功时返回一个文件描述符
    • 失败时返回-1,并设置全局变量errno指明失败原因

文件的关闭

  使用open函数打开的文件在操作结束后,应当使用close函数关闭。close函数的原型如下。

#include <unistd.h>
int close(int filedes);

  调用close函数后,终止了文件描述符与文件之间的关联,被关闭的文件描述符重新变为可用。关闭一个文件的同时也释放了该进程加在该文件上的所有记录锁。当一个进程终止时,它所打开的所有文件都由内核自动关闭。
  close函数的参数和返回值的含义如下:
1. filedes为待关闭的文件描述符
2. 返回值:成功时返回0,失败时返回-1


文件的读,写

  在Linux底层,可使用read函数读取已打开文件中的数据,用write函数写新的数据到已打开的文件中。
  write函数的原型:

#include <unistd.h>
ssize_t write(int fd, const void *buf, size_t count);

  各参数和返回值的含义如下:
- fd:文件描述符
- buf:待写入文件的数据的缓冲区, 是一个常量指针
- count:待写的字节数,该字节数应当小于或等于缓冲区大小
- 返回值:若成功,则返回已写的字节数,若出错,则返回-1,错误值记录在errno中

比如:

#include <unistd.h>
#include <stdlib.h>

int main()
{
    if ((write(1, "Here is some data\n", 18)) != 18)
        write(2, "A write error has occurred on file descriptor 1\n", 46);

    return 0;
}

解释:第六行,write函数向文件描述符1(也就是标准输出——屏幕)写入18字节的数据,如果出错了,也即是返回值不为18,则向文件描述符2(也就是标准错误输出)写入46字节的数据。程序的运行结果如下:

biantiao@lazybone1994-ThinkPad-E430:~/sh$ gcc -o ex_write ex_write.c
biantiao@lazybone1994-ThinkPad-E430:~/sh$ ./ex_write
Here is some data
biantiao@lazybone1994-ThinkPad-E430:~/sh$ 

  调用read函数可以从已打开的文件中读取数据,其原型如下:

#include <unistd.h>
ssize_t read(int fd, void *buf, size_t count);

  read函数各参数和返回值含义如下:
- buf:用于放置读取到的数据的缓冲区
- 返回值:若成功,返回已读取的字节数(0表示已到达文件尾),若出错,返回-1,错误记录在errno中。

比如:

#include <unistd.h>
#include <stdlib.h>

int main()
{
    char buffer[128];
    int nread;

    nread = read(0, buffer, 128);
    if (nread == -1){
        write(2, "A read error has occurred.\n", 26);
    }
    else if ((write(1, buffer, nread)) != nread){
        write(2, "A write error has occurred.\n", 27);
    }

    return 0;
}

解释:第9行调用read函数,从键盘读取最多128个字节(文件描述符为1),read返回实际读取到的字节数记录在nread中。第10行为判断是否出错。第12行调用write函数,将读取到的buffer中的数据重新输出到标准输出设备。如果输出的字节数与读取到的字节数不一致,则在第13行向标准错误输出设备输出错误提示。

biantiao@lazybone1994-ThinkPad-E430:~/sh$ gcc -o ex_read ex_read.c
biantiao@lazybone1994-ThinkPad-E430:~/sh$ ./ex_read
This is a sample example for read function
This is a sample example for read function
biantiao@lazybone1994-ThinkPad-E430:~/sh$

文件的定位

  文件的定位。先来说一些干货,算是普及知识。
  对于可随机访问的文件,如磁盘文件,人们往往希望能够按需定位到文件的某个位置进行读,写操作。这可以通过调用lseek函数来完成。
  实际上,每个已打开的文件都有一个与其相关联的“当前文件位移量”。通常,读,写操作都从当前文件位移量处开始,并在读,写完成后使位移量增加所读写的字节数。
  当打开一个文件时,如果指定O_APPEND选项,该位移量被设置为文件的长度,否则该位移量被设置为0。如果要随机得访问文件的内容,可调用lseek函数显示地定位一个已打开文件的“当前文件位移量”
  lseek函数的原型为:

#include <sys/types.h>
#include <unistd.h>
off_t lseek(int fd, off_t offset, int whence);

  lseek仅将当前的文件位移量记录在内核变量内,并不引起任何I/O操作。lseek函数各参数和返回值的含义如下:
- fd:文件描述符
- offset:位移量。off_t类型一般为“long int”的typedef
- whence:指定位移量相对于何处开始,可取下面三个值

- SEEK_SET:文件开始的位置
- SEEK_CUR:文件读写指针当前位置
- SEEK_END:文件结束位置
  • 返回值:若成功为当前读写位置相对于头文件的位移量;若出错为-1,错误值记录在errno中

  下面是一个综合实例:

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

char buf1[] = "abcdefghij";
char buf2[] = "ABCDEFGHIJ";

int main()
{
    int fd;

    /* 调用open函数以只写(O_WRONLY)和创建(O_CREAT)方式在当前目录创建一个所有者具有
       读(S_IRUSR)和写(S_IWUSR)权限的普通文件file.hole,打开的文件描述符记录于fd变量
       如果open的返回值小于0,说明打开文件失败,输出错误提示并退出程序
    */
    if ( (fd = open("file.hole", O_WRONLY | O_CREAT, S_IRUSR | S_IWUSR)) < 0 ){
        write(2, "create error.\n", 13);
        return -1;
    }

    /* 向文件写入“abcdefghij”,如果写入成功,则当前位移量为10 */
    if (write(fd, buf1, 10) != 10){
        write(2, "buf1 write error.\n", 17);
        return -1;
    }

    /* 当前的位移量为10,调用lseek函数,将当前位移量设置为40 */
    if (lseek(fd, 40, SEEK_SET) == -1){
        write(2, "lseek error.\n", 12);
        return -1;
    }

    /* 从当前位移量为40处开始写入“ABCDEFGHIJ” */
    if (write(fd, buf2, 10) != 10){
        write(2, "buf2 write error.\n", 17);
        return -1;
    }

    /* 写入成功后,当前位移量为50 */

    return 0;
}

  将该程序编译并运行结果如下:

biantiao@lazybone1994-ThinkPad-E430:~/sh/file$ gcc -o ex_lseek ex_lseek.c
biantiao@lazybone1994-ThinkPad-E430:~/sh/file$ ./ex_lseek
biantiao@lazybone1994-ThinkPad-E430:~/sh/file$ od -c file.hole
0000000   a   b   c   d   e   f   g   h   i   j  \0  \0  \0  \0  \0  \0
0000020  \0  \0  \0  \0  \0  \0  \0  \0  \0  \0  \0  \0  \0  \0  \0  \0
0000040  \0  \0  \0  \0  \0  \0  \0  \0   A   B   C   D   E   F   G   H
0000060   I   J
0000062
biantiao@lazybone1994-ThinkPad-E430:~/sh/file$

说明:od -c命令表示以字符形式显示二进制文件的内容。


读取文件的属性

  Linux提供了stat系列函数用来读取文件的属性信息。这些函数的原型如下:

#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
int stat(const char *file_name, struct stat *buf);
int fstat(int filedes, struct stat *buf);
int lstat(const char *file_name, struct stat *buf);

  这些函数各参数和返回值的含义如下:
- file_name:文件名
- filedes:文件描述符
- buf:文件信息结构缓冲区,该缓冲区为一个结构体,定义如下:

struct stat{
    dev_t       st_dev;         /* 保存本文件的设备的ID */
    ino_t       st_ino;         /* 与文件关联的索引节点号 */
    mode_t      st_mode;        /* 文件权限和文件类型信息 */
    nlink_t     st_nlink;       /* 该文件上硬链接的个数 */
    uid_t       st_uid;         /* 文件所有者的UID号 */
    gid_t       st_gid;         /* 文件所有者的GID号 */
    dev_t       st_rdev;        /* 特殊文件的设备ID */
    off_t       st_size;        /* 文件大小 */
    blksize_t   st_blksize;     /* 文件系统I/O的块大小 */
    blkcnt_t    st_blocks;      /* 块数 */
    time_t      st_atime;       /* 最后访问时间 */
    time_t      st_mtime;       /* 最后修改时间 */
    time_t      st_ctime;       /* 最后状态改变时间 */
};
  • 返回值:成功为0,若出错为-1,错误值记录在errno中

说明:stat和lstat函数的区别:当文件是一个符号链接时,lstat返回的是该符号链接本身的信息,而stat返回的是该链接指向的文件的信息。调用stat系列函数时,文件的属性信息均保存在struct stat的结构体类型的buf当中。

  看看一看st_mode成员。st_mode成员的每一个位代表了一种权限或文件类型,可以将该成员与下表中所示的标志位进行二进制“与”运算以测试文件权限或类型。

标志位 常量值 含义
S_IFMT 0170000 文件类型掩码
S_IFSOCK 0140000 套接字
S_IFLNK 0120000 符号链接
S_IFREG 0100000 普通文件
S_IFBLK 0060000 块设备
S_IFDIR 0040000 目录
S_IFCHR 0020000 字符设备
S_IFIFO 0010000 FIFO(命名管道)
S_ISUID 0004000 设置了SUID
S_ISGID 0002000 设置了SGID
S_ISVTX 0001000 设置了粘滞位
S_IRWXU 00700 文件所有者权限掩码
S_IRUSR 00400 文件所有者可读
S_IWUSR 00200 文件所有者可写
S_IXUSR 00100 文件所有者可执行
S_IRWXG 00070 文件所属组权限掩码
S_IRGRP 00040 文件所属组可读
S_IWGRP 00020 文件所属组可写
S_IXGRP 00010 文件所属组可执行
S_IRWXO 00007 其他用户权限掩码
S_IROTH 00004 其他用户可读
S_IWOTH 00002 其他用户可写
S_IXOTH 00001 其他用户可执行

  除了直接使用二进制与的方法进行文件类型测试外,Linux还提供了以下宏用于文件类型的判定(其中m参数即为st_mode成员)
- S_ISREG(m):是否为普通文件
- S_ISDIR(m):是否为目录
- S_ISCHR(m):是否为字符设备
- S_ISBLK(m):是否为块设备
- S_ISFIFO(m):是否为管道设备
- S_ISLNK(m):是否为符号连接
- S_ISSOCK(m):是否为套接字

实例:下面这个实例演示了如何使用stat函数

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

int main(int argc, char *argv[])
{
    struct stat sb;

    if (argc != 2){
        printf("Usage : %s <pathname>\n", argv[0]);
        exit(EXIT_FAILURE);
    }

    if (stat(argv[1], &sb) == -1){

        perror("stat");
        exit(EXIT_FAILURE);
    }

    printf("File type: ");
    switch (sb.st_mode & S_IFMT){
        case S_IFBLK : printf(" Block device.\n");          break;
        case S_IFCHR : printf(" Character device\n");       break;
        case S_IFDIR : printf(" Directory\n");              break;
        case S_IFIFO : printf(" FIFO/Pipe\n");              break;
        case S_IFLNK : printf(" Symlink\n");                    break;
        case S_IFREG : printf(" Regular file\n");           break;
        case S_IFSOCK : printf("Socket\n");                 break;
        default :      printf("Unknown file\n");            break;      
    }
    printf("I-node number : %ld\n", (long)sb.st_ino);
    printf("Mode : %lo (octal)\n", (unsigned long)sb.st_mode);
    printf("Link count : %ld\n", (long)sb.st_nlink);
    printf("Ownership : UID=%ld GID=%ld\n", (long)sb.st_uid, (long)sb.st_gid);
    printf("Preferred I/O block size : %ld bytes\n", (long)sb.st_blksize);
    printf("Blocks allocated : %lld\n",(long long)sb.st_blocks);

    printf("Last status change : %s", ctime(&sb.st_ctime));
    printf("Last file access : %s", ctime(&sb.st_atime));
    printf("Last file modification : %s", ctime(&sb.st_mtime));

    return 0;
}

  编译并运行

biantiao@lazybone1994-ThinkPad-E430:~/桌面$ gcc -o stat stat.c
biantiao@lazybone1994-ThinkPad-E430:~/桌面$ ./stat stat.c
File type:  Regular file
I-node number : 4476379
Mode : 100664 (octal)
Link count : 1
Ownership : UID=1001 GID=1001
Preferred I/O block size : 4096 bytes
Blocks allocated : 8
Last status change : Thu Aug 20 09:09:17 2015
Last file access : Thu Aug 20 09:09:18 2015
Last file modification : Thu Aug 20 09:09:17 2015
biantiao@lazybone1994-ThinkPad-E430:~/桌面$

  怎样在读取一个文件属性之前判断该文件是否存在呢?答案是使用access函数。
  access函数进行文件的存取许可测试,它的原型如下:

#include <unistd.h>
int access(const char *pathname, int mode);

  access函数按实际用户的ID和实际组ID进行存取测试,其各参数和返回值的含义如下:

1. pathname : 文件名
2. mode : 测试项,其值可以是以下之一个或多个值按位或的结果

- R_OK:测试读许可权
- W_OK:测试写许可权
- X_OK:测试执行许可权
- F_OK:测试文件是否存在

3. 返回值:成功返回0,失败返回-1

  下面是一个使用access函数的例子,功能是测试提供的文件是否存在并可读,如果不存在或不可读则输出相应的提示信息,如果可读则读取前20个字节的数据并输出:

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

int main(int argc, char *argv[])
{
    int i, num;
    int fd;
    char buf[20];

    if (argc != 2){
        printf("Usage : %s <pathname>\n", argv[0]);
        exit(EXIT_FAILURE);
    }

    if (access(argv[1], F_OK) != 0){
        printf("The file '%s' doesn't existed!\n", argv[1]);
        exit(EXIT_FAILURE);
    }

    if (access(argv[1], R_OK) != 0){
        printf("The file '%s' can not be read!\n", argv[1]);
        exit(EXIT_FAILURE);
    }

    if ((fd = open(argv[1], O_RDONLY)) < 0){
        printf("Failed to open file '%s' for read!\n", argv[1]);
        exit(EXIT_FAILURE);
    }

    if ((num = read(fd, buf, 20)) < 0){
        close(fd);
        printf("Failed to read file '%s'!\n", argv[1]);
        exit(EXIT_FAILURE);
    }

    printf("The starting %d bytes of '%s' is :\n", num, argv[1]);
    for (i = 0; i < num; i++){
        printf("%c", buf[i]);
    }
    printf("\n");
    close(fd);

    return 0;
}

  编译并运行

biantiao@lazybone1994-ThinkPad-E430:~/桌面$ gcc -o access access.c
biantiao@lazybone1994-ThinkPad-E430:~/桌面$ ./access
Usage : ./access <pathname>
biantiao@lazybone1994-ThinkPad-E430:~/桌面$ ./access access.c
The starting 20 bytes of 'access.c' is :
#include <sys/types.
biantiao@lazybone1994-ThinkPad-E430:~/桌面$
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章