文件操作
Linux文件结构
在Linux中,一切(或几乎一切)都是文件。
目录
系统使用的是文件的 inode编号 (保存了文件的管理信息,是文件系统中的一个特殊的数据块),目录结构为文件命名仅仅是为了便于人们使用。
目录是用于保存其他文件的节点号和名字的文件。目录文件中的 每个数据项都是指向某个文件节点的链接 ,删除文件名就等于删除与之对应的链接(文件节点号可以通过 ls -i 查看)。可以用 ln 创建指向同一个文件的链接。
删除一个文件时,实质上是删除了该文件对应的目录项,同时指向该文件的链接数减1。当 链接数为0时,就表示该节点以及其指向的数据不再被使用,磁盘上的相应位置就会被标记为可用空间。
文件和设备
硬件设备在Linux中通常也被表示(映射)为文件。
挂载命令:mount
比较重要的三个设备文件:
(1)/dev/console
系统控制台。错误信息和诊断信息通常会被发送到这个设备。
(2)/dev/tty
控制终端。
(3)/dev/null
空设备。写向这个设备的输出被丢弃。
设备分为 字符设备 和 块设备。两者的区别在于访问设备时是否需要一次读写一整块。一般情况下,块设备是那些支持某些类型文件系统的设备,例如硬盘。
系统调用和设备驱动程序
用很少量的函数就可以对文件和设备进行访问和控制,这些函数被称为 系统调用,由UNIX(Linux)直接提供,它们也是通向操作系统本身的接口。
操作系统的核心部分,即 内核,是一组 设备驱动程序。它们是一组对系统硬件进行控制的底层接口。
用于访问设备驱动程序的底层函数(系统调用):
open:打开文件或设备。
read:从打开的文件或设备里读数据。
write:向文件或设备写数据。
close:关闭文件或设备。
ioctl:把控制信息传递给设备驱动程序。
库函数
针对输入输出操作直接使用底层系统调用的一个问题是它们的效率非常低:
(1)使用系统调用会影响系统的性能。
(2)硬件会限制底层系统调用一次所能读写的数据块大小。
为了给设备和磁盘文件提供更高层的接口,Linux发行版提供了一系列的标准函数库。它们是一些由函数构成的集合。
底层文件访问
每个运行中的程序被称为 进程(process)。
可能通过系统调用open把文件描述符与文件和设备相关联。
write系统调用
#include <unistd.h>
size_t write(int fildes, const void *buf, size_t nbytes);
将缓冲区buf的前nbytes个字节写入与文件描述符fildes关联的文件中,返回实际写入字节数。如果返回-1,就表示出现错误,错误代码保存在全局变量errno里。
read系统调用
#include <unistd.h>
size_t read(int fildes, void *buf, size_t nbytes);
从与文件描述符fildes关联的文件中读入nbytes个字节的数据,并把它们放到数据区buf中。返回实际读入的字节数,返回-1,就表示出现错误。
open系统调用
//在遵循POSIX规范的系统上,不需要包含sys/types.h和sys/stat.h,但在某些UNIX系统上,需要包含
#include <fcntl.h>
#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);
建立一条到文件或设备的访问路径,成功时返回文件描述符,失败返回-1,并设置errno。
oflags参数是通过命令文件访问模式与其他可选模式相结合(用“|”操作)的方式来指定的。
模式 | 说明 |
---|---|
O_RDONLY | 以只读方式打开 |
O_WRONLY | 以中写方式打开 |
O_RDWR | 以读写方式打开 |
可选模式 | 说明 |
---|---|
O_APPEND | 把写入数据追加在文件的末尾 |
O_TRUNC | 把文件长度设置为零,丢弃已有的内容 |
O_CREAT | 如果需要,就按参数mode中给出的访问模式创建文件 |
O_EXCL | 与O_CREAT一起使用,确保调用者创建出文件。可以防止两个程序同时创建同一个文件 |
任何一个运行中的程序能够同时打开的文件数是有限制的,这个限制通常是由limits.h头文件中的常量OPEN_MAX定义的。
当你使用带有O_CREAT标志的open调用来创建文件时,必须使用有3个参数格式的open调用。第三个参数mode是几个标志按位或后得到的,这些标志在头文件 sys/stat.h 中定义。
mode_t 取值 | 说明 |
---|---|
S_IRUSR | 读权限,文件属主。 |
S_IWUSR | 写权限,文件属主。 |
S_IXUSR | 执行权限,文件属主。 |
S_IRGRP | 读权限,文件所属组。 |
S_IWGRP | 写权限,文件所属组。 |
S_IXGRP | 执行权限,文件所属组。 |
S_IROTH | 读权限,其他用户。 |
S_IWOTH | 写权限,其他用户。 |
S_IXOTH | 执行权限,其他用户。 |
close系统调用
#include <unistd.h>
int close(int fildes);
终止描述符fildes与其对应文件之间的关联。成功时返回0,出错时返回-1。
ioctl系统调用
#include <unistd.h>
int ioctl(int fildes, int cmd, ...);
具体设备具体情况。
其他系统调用
lseek系统调用
#include <unistd.h>
#include <sys/types.h>
off_t lseek(int fildes, off_t offset, int whence);
对文件描述符fildes的读写指针进行设置。
offset参数用来指定位置,whence参数定义该偏移值的用法。
参数whence取值 | 说明 |
---|---|
SEEK_SET | offset是一个绝对位置(即相当于文件头的相对位置) |
SEEK_CUR | offset是相对于当前位置的一个相对位置 |
SEEK_END | offset是相对于文件尾的一个相对位置 |
返回从文件头到文件指针被设置处的字节偏移值,失败时返回-1。
参数offset的类型 off_t 是一个与具体实现有关的 整数 类型,它定义在头文件 sys/types.h 中。
fstat、stat和lstat系统调用
#include <unistd.h>
#include <sys/stat.h>
#include <sys/types.h>
int fstat(int fildes, struct stat *buf);
int stat(const char *path, struct stat *buf);
int lstat(const char *path, struct stat *buf);
返回文件的状态信息,并放入到buf中。
当文件是一个符号链接时,lstat返回的是该符号链接本身的信息,而stat返回的是该链接指向的文件的信息。
struct stat 成员 | 说明 |
---|---|
st_mode | 文件权限和文件类型信息 |
st_ino | 与该文件关联的inode |
st_dev | 保存文件的设备 |
st_uid | 文件属主的UID号 |
st_gid | 文件属主的GID号 |
st_atime | 文件上一次被访问的时间 |
st_ctime | 文件的权限、属主、组或内容上一次被改变的时间 |
st_mtime | 文件的内容上一次被修改的时间 |
st_nline | 该文件上硬链接的个数 |
st_mode标志还有一些与之关联的宏,它们定义在头文件 sys/stat.h 中。这些宏对访问权限、文件类型标志以及一些用于帮助测试特定类型和权限的掩码的定义。
访问权限标志与open一样。
文件类型标志 | 说明 |
---|---|
S_IFBLK | 文件是一个特殊的块设备 |
S_IFDIR | 文件是一个目录 |
S_IFCHR | 文件是一个特殊的字符设备 |
S_IFIFO | 文件是一个FIFO(命名管道) |
S_IFREG | 文件是一个普通文件 |
S_IFLNK | 文件是一个符号链接 |
其他模式标志 | 说明 |
---|---|
S_ISUID | 文件设置了SUID位 |
S_ISGID | 文件设置了SGID位 |
解释st_mode标志的掩码 | 说明 |
---|---|
S_IFMT | 文件类型 |
S_IRWXU | 属主的读/写/执行权限 |
S_IRWXG | 属组的读/写/执行权限 |
S_IRWXO | 其他用户的读/写/执行权限 |
帮助确定文件类型的宏定义 | 说明 |
---|---|
S_ISBLK | 测试是否是特殊的块设备文件 |
S_ISCHR | 测试是否是特殊的字符设备文件 |
S_ISDIR | 测试是否是目录 |
S_ISFIFO | 测试是否是FIFO |
S_ISREG | 测试是否是普通文件 |
S_ISLNK | 测试是否是符号链接 |
dup和dup2系统调用
#include <unistd.h>
int dup(int fildes);
int dup2(int fildes, int fildes2);
dup复制文件描述符fildes,返回一个新的描述符。
dup2通过明确指定目标描述符来把一个文件描述符复制为另一个。
标准I/O库
在很多方面,使用标准I/O库的试和使用底层文件描述符一样。在标准I/O库中,与底层文件描述符对应的是流,它被实现为指向结构FILE的指针。
在启动程序时,有三个文件流是自动打开的:stdin、stdout和stderr,分别对应底层文件描述符的0、1和2。
fopen函数
#include <stdio.h>
FILE *fopen(const char *filename, const char *mode);
失败时返回NULL,NULL值在头文件stdio.h里定义。
参数mode取值 | 说明 |
---|---|
“r” 或 “rb” | 以只读方式打开 |
“w"或"wb” | 以写方式打开,并把文件长度截短为零 |
“a"或"ab” | 以写方式打开,新内容追加在文件尾 |
"r+"或"rb+“或"r+b” | 以更新方式打开(读和写) |
"w+"或"wb+“或"w+b” | 以更新方式打开,并把文件长度截短为零 |
"a+"或"ab+“或"a+b” | 以更新方式打开,新内容追加在文件尾 |
fread函数
#include <stdio.h>
size_t fread(void *ptr, size_t size, size_t nitems, FILE *stream);
数据从文件流stream读到ptr指向的数据缓冲区里,size表示每个数据记录的长度,nitems给出要传输的记录个数。
成功时返回成功读取的 记录个数(不是字节数)。
fwrite函数
#include <stdio.h>
size_t fwrite(const void *ptr, size_t size, size_t nitems, FILE *stream);
与fread类似。
fclose函数
#include <stdio.h>
int fclose(FILE *stream);
成功时返回0,否则返回EOF(-1)。
fflush函数
#include <stdio.h>
int fflush(FILE *stream);
把文件流中所有未写出的数据立刻写出。调用fclose函数隐含执行了一次flush操作。
fseek函数
#include <stdio.h>
int fseek(FILE *stream, long int offset, int whence);
与lseek系统调用完全一样,但返回的是一个整数:0表示成功,-1表示失败。
fgetc、getc和getchar函数
#include <stdio.h>
int fgetc(FILE *stream);
int getc(FILE *stream);
int getchar();
成功时返回下一个字节,失败(到达文件尾或出现错误,可以通过ferror或feof判断)时返回EOF。
getc作用和fgetc一样,但getc被实现为一个宏。
getchar相当于getc(stdin)。
fputc、putc和putchar函数
#include <stdio.h>
int fput(int c, FILE *stream);
int putc(int c, FILE *stream);
int putchar(int c);
与fgetc、getc和getchar类似。
fgets和gets函数
#include <stdio.h>
char *fgets(char *s, int n, FILE *stream);
char *gets(char *s);
fgets不丢弃换行符,gets丢弃换行符。
fgets最多读取(n - 1)个字符,再加上’\0’。
gets没有长度限制,可能会溢出缓冲区,所以最好用fgets。
格式化输入和输出
printf、fprintf和sprintf函数
#include <stdio.h>
int printf(const char *format, ...);
int sprintf(char *s, const *format, ...);
int fprintf(FILE *stream, const char *format, ...);
printf相当于fprintf(stdout, const char *format, …)。
scanf、fscanf和sscanf函数
#include <stdio.h>
int scanf(const char *format, ...);
int fscanf(FILE *stream, const char *format, ...);
int sscanf(const char *s, const char *format, ...);
与printf、fprintf和sprintf类似。
其他流函数
函数 | 说明 |
---|---|
fgetpos | 获得文件流的当前位置 |
fsetpos | 设置文件流的当前位置 |
ftell | 返回文件流当前位置的偏移值 |
rewind | 重置文件流的位置 |
freopen | 重新使用一个文件流 |
setvbut | 设置文件流的缓冲机制 |
remove | 相当于unlink函数但它的path参数是一个目录的话,其作用就相当于rmdir函数 |
文件流错误
#include <errno.h>
extern int errno;
函数调用失败时可以通过errno指出原因。
#include <stdio.h>
int ferror(FILE *stream);
int feof(FILE *stream);
void clearerr(FILE *stream);
前两者如果标识被设置则返回非零值,否则返回零。
clearerr可以清除错误标识,可以通过使用它从文件流的错误状态中恢复。例如,在“磁盘已满”错误解决之后,继续开始写入文件流。
文件流和文件描述符
#include <stdio.h>
int fileno(FILE *stream);
FILE *fdopen(int fildes, const char *mode);
文件和目录的维护
chmod系统调用
#include <sys/stat.h>
int chmod(const char *path, mode_t mode);
更改权限。
chown系统调用
#include <sys/types.h>
#include <unistd.h>
int chown(const char *path, uid_t owner, gid_t group);
更改所属。
unlink、link和symlink系统调用
#include <unistd.h>
int unlink(const char *path);
int link(const char *path1, const char *path2);
int symlink(const char *path1, const char path2);
unlink删除文件(链接数-1)。成功时返回0,失败时返回-1。
link系统调用将创建一个指向已有文件path1的新链接。新目录项由path2给出。
symlink与link类似,但创建的是符号链接。
mkdir和rmdir系统调用
#include <sys/types.h>
#include <sys/stat.h>
int mkdir(const char *path, mode_t mode);
创建目录。
#include <unistd.h>
int rmdir(const char *path);
删除目录(只能是空目录)。
chdir系统调用和getcwd函数
#include <unistd.h>
int chdir(const char *path);
切换目录。
#include <unistd.h>
char *getcwd(char *buf, size_t size);
把当前目录的名字写到给定的缓冲区buf里。如果目录名的长度超出了参数size(ERANGE错误)或程序运行过程中目录被删除(EINVAL错误)或有关权限发生了变化(EACCESS错误),它就返回NULL,如果成功就返回buf。
扫描目录
与目录操作有关的函数在 dirent.h 头文件中声明。
被称为目录流的指向这个结构的指针(DIR *)被用来完成各种操作,其使用方法与用来操作普通文件的文件流(FILE *)非常相似。
目录数据项本身则在dirent结构中返回,这是因为用户不应直接改动DIR结构中的数据字段。
opendir函数
#include <sys/types.h>
#include <dirent.h>
DIR *opendir(const char *name);
readdir函数
#include <sys/types.h>
#include <dirent.h>
struct dirent *readdir(DIR *dirp);
struct dirent 成员类型 | struct dirent 成员名称 | 说明 |
---|---|---|
ino_t | d_ino | 文件的inode节点号 |
char* | d_name | 文件的名字 |
想要进一步了解目录中的某个文件,需要使用stat调用。
telldir函数
#include <sys/types.h>
#include <dirent.h>
long int telldir(DIR *dirp);
返回目录流里的当前位置。
seekdir函数
#include <sys/types.h>
#include <dirent.h>
void seekdir(DIR *dirp, long int loc);
设置目录流位置。
closedir函数
#include <sys/types.h>
#include <dirent.h>
int closedir(DIR *dirp);
错误处理
错误代码和取值都在 errno.h 里定义。
错误代码 | 说明 |
---|---|
EPERM | 操作不允许 |
ENOENT | 文件或目录不存在 |
EINTR | 系统调用被中断 |
EIO | IO错误 |
EBUSY | 设备或资源忙 |
EEXIST | 文件存在 |
EINVAL | 无效参数 |
EMFILE | 打开文件过多 |
ENODEV | 设备不存在 |
EISDIR | 是一个目录 |
ENOTDIR | 不是一个目录 |
strerror函数
#include <string.h>
char *strerror(int errnum);
把错误代码映射成字符串。
perror函数
#include <stdio.h>
void perror(const char *s);
把errno中的错误映射成字符串,并在前面加上s和": ",然后输出到标准错误输出流。
/proc文件系统
/proc中包含了许多特殊文件用来对驱动程序和内核信息进行更高层的访问。