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
mode:存取许可权位,一个32位无符号整数,仅当创建新文件时才使用,由下列一个或多个常数进行或运算构成。应注意,最终文件权限受系统变量umask限制,是所设权限和umask的二进制“非”进行二进制“与”所得的结果。
- S_IRUSR:文件所有者读
- S_IWUSR:文件所有者写
- S_IXUSR:文件所有者执行
- S_IRGRP:用户组读
- S_IWGRP:用户组写
- S_IXGRP:用户组执行
- S_IROTH:其它用户读
- S_IWOTH:其它用户写
- S_IXOTH:其它用户执行
返回值
- 成功时返回一个文件描述符
- 失败时返回-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:~/桌面$