【Linux环境编程入门】四、文件操作的系统调用

来源:互联网 发布:知乎lol dota 编辑:程序博客网 时间:2024/05/16 11:48

本系列文章系本人原创,欢迎转载,转载请注明出处

注:这篇文章所建立的所有文件(源文件,库文件,可执行文件等)均可在这里下载。下载的是chapter4.tar.gz文件,可以在Linux系统终端中用如下命令解压到当前目录:

tar zxvf chapter4.tar.gz

在Linux系统中,几乎可以认为一切皆文件。所以文件的操作是非常频繁且至关重要的。在这一章中,我们来介绍一下Linux系统下的文件操作相关的程序设计。

一、文件的权限

在讲解文件操作之前,我们先简单的介绍下Linux系统上的文件权限。
在任意目录下输入下列命令:

ls -l

将会有类似如下的输出:
这里写图片描述

我们先看第一行:

drwxrwxr-x. 2 test test  104 Nov  3 21:21 example

第一列的第一个字符 “d”表示该行的文件(example)是一个目录。第一列接下来的 “rwxrwxr-x”可以分为三组:
第一组 “rwx”: 对应文件(这里的example目录)的所有者的权限,所有者即第三列的 “test”用户。
第二组 “rwx”:对应文件的所属组的权限,所属组即第四列的 “test”组。
第三组 “r-x”:其他用户的权限。

那么每组下的rwx是什么意思呢?
r: 表示对应的用户/组有写的权限
w: 表示对应的用户/组有读的权限
x: 表示对应的用户/组有执行文件的权限

对于通常意义下的文件,这三个权限很好理解,那么对于目录,又该怎样理解呢? 其实我们可以简单的理解为既然Linux下几乎一切皆文件,那么目录也是文件,目录的内容就是目录下的那些文件,这样就不难理解目录的rwx权限分别对应什么操作了:
r: 查询该目录下的文件(如 “ls 目录”)
w: 修改目录下的目录结构,如删除文件,重命名文件,新建文件等
x: 表示能够进入该目录(如 “cd 目录”)

二、文件的读写

在C语言的标准I/O库(stdio)中,也有许多I/O相关的函数,这里我们不做探讨,有兴趣的读者可以查阅C语言相关资料。我们在这一章中,主要探讨Linux文件的底层系统调用。(C语言的标准I/O库已经提供了相当多的功能,但是如果你需要对文件操作进行精确的控制,则底层系统调用会比较合适)。

在讲文件操作的系统调用前,我们先来讲一下文件描述符。这是一些整数值,它们对应到具体的文件。当程序想操作某个文件的时候,只要向相应的函数里传入文件描述符就可以了。我们举例说明一下:

每一个进程一般都会有如下三个已经打开的文件描述符:
0:标准输入
1:标准输出
2:异常输出
当一个进程想向标准输出(一般为终端或控制台)输出字符时,便可通过文件描述符2来进行输出。读者可以在下面讲解系统调用的时候体会文件描述符的意义。

open系统调用

open系统调用的原型如下:

#include <unistd.h>//在遵循POSIX规范的系统上,下面两个头文件不是必须的#include <sys/types.h>#include <sys/stat.h>int open(const char *path, int oflags);int open(const char *path, int oflags, mode_t mode);

我们来逐一分析open函数的参数和返回值(详细的文档可以在终端输入 “man 2 open”查看):
最一目了然的是path,这个就是我们要打开的文件的地址 (如 “/home/test/test.txt”),而返回的int类型的值便是我们上面提到的文件描述符,我们把这个值传到下面要讲到的read和write函数,read和write函数便能知道我们要读/写的是哪个文件了。

接下来我们看oflags参数,这是一个整形值,它是用于指定打开文件的方式:
O_RDONLY: 以只读方式打开
O_WRONLY: 以只写方式打开
O_RDWR: 以读写方式打开
oflags必须指定上面三个值中的一个,以确定打开文件的方式,除此之外,还有下面的可选值用于指定更多的详细的方式:
O_APPEND: 对文件进行追加写入(从文件尾写入)
O_TRUNC: 丢弃文件已有的内容,从头写入
O_CREAT: 如果需要的话,结合第三个参数mode,创建文件
O_EXCL: 结合O_CREAT一起使用,可以理解为 “排他”模式,即如果文件已经存在,open调用将会失败,可以防止两个程序同时创建一个文件。
还有更多的可选方式,具体可以查阅open调用的文档。

在上面我们提到了O_CREAT方式要和第三个参数mode一起使用,现在我们来讲解mode参数。mode参数主要是给文件设置权限的,在上面讲到文件权限的时候我们知道文件权限分三个组,每个组下有三种权限,所以mode参数一共有9个选值:
S_IRUSR: 所有者的读权限
S_IWUSR: 所有者的写权限
S_IXUSR: 所有者的执行权限
S_IRGRP: 所属组的读权限
S_IWGRP: 所属组的写权限
S_IXGRP: 所属组的执行权限
S_IROTH: 其他用户的读权限
S_IWOTH: 其他用户的写权限
S_IXOTH: 其他用户的执行权限
这9个名字看上去很难记,其实是很有规律的:即 “S_I(R/W/X)” + “USR/GRP/OTH”拼出来的,一共9个,RWX分别就是读/写/执行的权限,而USR/GRP/OTH分别是所有者(User)/所属组(Group)/其他用户(Other)。

close系统调用
#include <unistd.h>int close(int fildes);

close系统调用很简单,顾名思义,它会关闭文件描述符fildes。如果调用成功则返回0,否则返回-1.
接下来我们来看一个例子来展示open和close两个系统调用,源码如下:

#include <unistd.h>#include <sys/types.h>#include <sys/stat.h>//O_WRONLY, O_CREAT在下面这个头文件中定义#include <fcntl.h>int main(){        int fildes=open("test.txt", O_WRONLY | O_CREAT,                S_IRUSR | S_IWUSR | S_IXUSR                |S_IRGRP | S_IWGRP | S_IXGRP                |S_IROTH | S_IWOTH | S_IXOTH);        int value=close(fildes);        return value;}

这个程序就是在当前目录下创建了一个名为 “test.txt”的文件,并让所有者/所属组/其他用户都对该文件具有读写执行的权力。用该源码生成可执行文件并执行它,然后用 “ls -l”命令查看 “test.txt”文件,可能会和我们预想的不一致,在笔者的电脑上,结果如下:
这里写图片描述

我们注意到,在笔者的电脑上,test.txt文件的权限是rwxrwxr-x,为什么其他用户没有写权限呢?这是因为笔者的Linux的umask值是002(可以通过直接在终端输入umask查看umask值),它屏蔽了其他用户的写权限。

原因是这样的:我们的程序在创建这个文件的时候试图赋予的权限是”rwxrwxrwx”,即所有用户的读写执行权限都赋予了,我们用1表示有权限,0表示没有权限,则我们试图给test.txt赋予的权限是 “111 111 111”,而umask是002,第一位的0对应所有者,第二位的0对应所属组,第三位的2对应其他用户,所以umask的值是 “000 000 010”,把这个值按位取反,得到 “111 111 101”,这个值再与我们试图赋予test.txt的权限 “111 111 111”做按位与运算,得到 的”111 111 101”就是test.txt的最终权限。
我们也可以简单的理解, umask值 ” 000 000 010”中哪一位是1,那么文件权限中的哪一位就被屏蔽掉了。

读者可以自行将umask的值改为其他的,再执行上面的程序,比较一下新生成的test.txt的权限便能更好地理解umask了。

read系统调用
#include <unistd.h>size_t read(int fildes, void *buf, size_t nbytes);

我们来逐一讲解该函数的参数及返回值:
fildes: 要读的文件的文件描述符
buf: 读进来数据会放进buf
nbytes: 从fildes对应的文件中要读取的字节数
返回值为size_t类型,代表实际读入的字节数。

write系统调用
#include <unistd.h>size_t write(int fildes, const void *buf, size_t nbytes);

下面是对write系统调用的参数及返回值的说明:
fildes: 要写的文件的文件描述符
buf: 要写的内容存在这里,可以看作是数据缓冲区
nbytes: 要写的字节数,即把buf中的前nbytes个字节写到fildes对应的文件中。
返回值是size_t类型,代表实际写入的字节数。

例子

在这个例子中,我们从标准输入中读入数据,并将其输出到当前目录下的data文件中,如果data文件不存在,则创建它。
源码如下:

#include <unistd.h>#include <sys/types.h>#include <sys/stat.h>#include <fcntl.h>int main(){        const int SIZE=256;        char buffer[SIZE];        size_t count;        int fildes=open("data",O_WRONLY | O_CREAT | O_APPEND, S_IRUSR | S_IWUSR);        while(count=read(0,buffer,sizeof(buffer)))        {                write(fildes, buffer, count);        }        close(fildes);        return count;}

用这段源码生成可执行文件并运行,效果如下图(图中cat命令用于将文件内容输出到终端,可以方便的查看文件内容):
这里写图片描述

1 0
原创粉丝点击