大概是一年前学习的Linux文件IO,现在整理一下。Linux下一切皆文件,我们对Linux下任何设备的操作都是对文件的操作,学习了Linux驱动开发就会对“文件操作”有很强烈的感觉。Linux的虚拟文件系统给我们提供了大量的对文件进行操作的系统调用:
- 文件描述符
文件描述符(file descriptor, fd)是Linux内核为了高效管理已被打开的文件所创建的索引,其是一个非负整数(通常是小整数),用于指代被打开的文件,所有执行I/O操作的系统调用都通过文件描述符。程序在开始运行时,系统会自动打开三个文件描述符,0是标准输入,1是标准输出,2是标准错误。POSIX标准要求每次打开文件时(含socket)必须使用当前进程中最小可用的文件描述符号码,因此第一次打开的文件描述符一定是3.
- Open
原型:
int open(const char *pathname, int flags, mode_t mode);
或:
int open(const char *pathname, int flags);
头文件包含:
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
第一个参数:要打开的文件,可指定路径
第二个参数多选:为多个宏的位运算结果:
必须参数:O_RDONLY(只读打开),O_WRONLY(只写打开)和O_RDWR(读写打开)
可选参数:O_APPEND(如果文件存在,打开后光标移至文件尾,即每次写都追加到文件尾,追加打开的文件如果文件中本来有内容读是读不到内容的)。
O_CREAT(如果文件不存在,就创建它)
O_TRUNC(文件存在,打开方式为只写或读写时,选择此参数会将文件长度截为0,即清空原本内容,重新写)。
O_NONBLOCK(这个参数可能刚接触时很少用到,但对于驱动层和对Linux管理来说,这个参数很重要,如果打开的文件为一个FIFO或者块设备,字符设备,此选项打开的文件,后续操作都会被设置为非阻塞模式,如对打开这个文件后,对此文件的read和write等操作,不会阻塞,而会立刻返回,在网络socket中使用这个参数后的效果很明显)
还有一些不常用的参数如O_EXEC、O_SEARCH、O_CLOEXEC、O_NOCTTY…
第三个参数:带O_CREAT选项时可以用来创建文件,这时必须带该参数用来指定创建文件的权限模式,如066。 否则不需要。
返回值:为一个文件描述符,此后对此文件文件描述符的操作,都可抽象为对此文件的操作。
- creat
int creat(const char *path, mode_t mode);
此函数用来创建一个新文件并返回其fd。它等价于 open(path, O_WRONLY|O_CREAT|O_TRUNC, mode),记住creat不是create。
- close
int close(int fd);
该函数用来关闭一个打开的文件描述符,关闭一个文件时还会释放该进程加在该文件上的所有记录锁。当一个进程终止时,
内核将会自动关闭它所有打开的文件。
- write
ssize_t write(int fd, const void *buf, size_t nbytes);
write()函数用来往打开的文件描述符fd指向的文件中写入buf指向的数据,其中nbytes指定要写入的数据大小。如果返回值<0则说明写入出错,譬如尝试往一个只读的文件中写入则会抛错,错误的原因系统会保存到errno变量中去。如果>0则为实际写入的数据大小。
- read
ssize_t read(int fd, void *buf, size_t nbytes);
read()函数用来从打开的文件描述符对应的文件中读取数据放到buf指向的内存空间中去,最多不要超过nbytes个字节,这里的nbytes一般是buf剩余的空间大小。如read成功,则返回实际读到的字节数(由nbytes或读到文件尾决定,其中EOF宏用来判断是否到了文件尾),如果返回值小于0则表示出错,如尝试读一个没有权限读的文件时就会抛错。
- lseek
off_t lseek(int fd, off_t offset, int whence);
我们在从文件里读出内容,或往文件写如内容的时候都有一个起始地址,这个起始地址就是当前文件偏移量(也就是我前面说的光标),当我们对文件进行读写的时候都会使文件偏移量往后偏移。这点就类似于我们打开记事本开始编辑文本时的光标,我们读或写入时从光标所在位置开始读写,每读写一个字节都会使光标往后偏移。通过lseek()这个函数我们可以调整文件偏移量的地址。其中 whence 可以是以下三个值:
而offset就是相对于whence 的偏移量,譬如:
lseek(fd, 0, SEEK_SET); 将文件偏移量设置到了文件开始的第一个字节上;
lseek(fd, 0, SEEK_END); 将文件偏移量设置到文件最后一个字节上;
lseek(fd, -1, SEEK_END); 将文件偏移量设置到文件最后的倒数第一个字节上;
- dup() 和 dup2()系统调用
int dup(int fd);
int dup2(int fd, int fd2);
这两个函数都可以用来复制一个新的文件描述符来指向fd对应的文件。这两个系统调用经常用在标准输入、标准输出、标准出错重定向。
示例程序:
#include <sys/stat.h>
#include <sys/types.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdio.h>
#include <string.h>
#define MAX_SIZE 128
int main(int argc,char **argv)
{
int fd1,fd2;
char buf[MAX_SIZE]={0};
const char *str="this is something writed to dup_fd\n";
fd1=open("test_dup",O_CREAT|O_TRUNC|O_RDWR,0666);//打开方式不存在就创建,可读可写,存在将长度截为0,此文件权限为666
fd2=open("test_dup2",O_CREAT|O_TRUNC|O_RDWR,0666);
if(fd1<0||fd2<0)
{
printf("open error\n");
close(fd1);
close(fd2);
return -1;
}
int dup_fd=dup(fd1);//产生一个新的文件描述符指向fd1指向的文件
close(fd1);//关闭fd1,此时还有dup_fd指向文件“test_dup”
dup2(fd2,STDOUT_FILENO);
dup2(fd2,STDERR_FILENO);
dup2(fd2,STDIN_FILENO);//重定向标准输入,输出,出错到文件"test_dup2"
write(dup_fd,str,strlen(str)+1);//向dup_fd所指向的文件写入数据str
int rv=read(dup_fd,buf,sizeof(buf));//从dup_fd指向的文件读出MAX_SIZE个数据
printf("before lseek, read %d bytes data from dup_fd:%s\n",rv,buf);
lseek(dup_fd,0,SEEK_SET);//定位文件偏移量到文件首
rv=read(dup_fd,buf,sizeof(buf));//再读
printf("after lseek, read %d bytes data from dup_fd:%s\n",rv,buf);
write(STDERR_FILENO,str,strlen(str)+1);//向标准出错文件描述符写入str
printf("this is something printed to the screen\n");//打印字符串到标准输出
close(fd2);//关闭文件描述符
close(dup_fd);
return 0;
}
编译运行结果:
root@Ubuntu-14:/home/zhanghang/github/My_code/APUE/file_IO# gcc dup.c
root@Ubuntu-14:/home/zhanghang/github/My_code/APUE/file_IO# ./a.out
root@Ubuntu-14:/home/zhanghang/github/My_code/APUE/file_IO# cat test_dup
this is something writed to dup_fd
root@Ubuntu-14:/home/zhanghang/github/My_code/APUE/file_IO# cat test_dup2
this is something writed to dup_fd
before lseek, read 0 bytes data from dup_fd:
after lseek, read 36 bytes data from dup_fd:this is something writed to dup_fd
this is something printed to the screen
- stat()和fstat()系统调用
int stat(const char * restrict path, struct stat *restrict buf);
int fstat(int fd, struct stat *buf);
这两个函数都是用来返回文件或目录的相关信息,只是stat()的第一个参数是文件名,而fstat()的第一个参数是文件打开的相应文件描述符。其中struct stat结构体的定义如下:
struct stat {
dev_t st_dev; /* ID of device containing file */
ino_t st_ino; /* inode number */
mode_t st_mode; /* protection */
nlink_t st_nlink; /* number of hard links */
uid_t st_uid; /* user ID of owner */
gid_t st_gid; /* group ID of owner */
dev_t st_rdev; /* device ID (if special file) */
off_t st_size; /* total size, in bytes */
blksize_t st_blksize; /* blocksize for filesystem I/O */
blkcnt_t st_blocks; /* number of 512B blocks allocated */
time_t st_atime; /* time of last access */
time_t st_mtime; /* time of last modification */
time_t st_ctime; /* time of last status change */
};
- access()系统调用
int access(const char *path, int mode);
access()可以用来测试文件是否存在或测试其权限位,其中第一个参数path是相应的文件路径名,第二个参数是要测试的模式。其中mode说明如下:
示例代码:
#include <stdlib.h>//system
#include <unistd.h>//access
#include <stdio.h>//snprintf printf
#define FILE_NAME 64
int main()
{
const char *name="test.txt";
char buf[FILE_NAME];
snprintf(buf,FILE_NAME,"touch %s",name);
if(system(buf)==-1)//system系统调用会在当前路径执行名为buf的shell命令,返回-1为执行失败,此处我执行的是在当前路径执行命令"touch test.txt"
{
printf("system error\n");
return -1;
}
if(access(name,F_OK))
{
printf("file %s does not exist\n",name);
return -1;
}
if(!access(name,R_OK))
{
printf("file %s is readable\n",name);
}
if(!access(name,W_OK))
{
printf("file %s is writable\n",name);
}
if(!access(name,X_OK))
{
printf("file %s is excutable\n",name);
}
return 0;
}
编译运行结果:
root@Ubuntu-14:/home/zhanghang/github/My_code/APUE/file_IO# gcc access.c
root@Ubuntu-14:/home/zhanghang/github/My_code/APUE/file_IO# ./a.out
file test.txt is readable
file test.txt is writable
root@Ubuntu-14:/home/zhanghang/github/My_code/APUE/file_IO# ls -l test.txt
-rw-r--r-- 1 root root 0 Jul 26 23:56 test.txt
-unlink()系统调用
该系统调用可以用来删除文件,其本质是让文件的链接记数自减。调用该函数将path指定的文件的链接数减1,如果对该文件还有其他链接存在,则仍可以通过其他链接访问该文件的数据。只有当链接记数达到0时,该文件的内容才可被删除。如果有进程打开了该文件,其内容也不能被删除。关闭一个文件时,内核首先检查打开该文件的进程个数,如果这个记数达到0,内核再去检查它的链接记数,如果记数也是0,那么就删除该文件内容。
- rename()系统调用
int rename(const char *oldname, const char *newname);
该系统调用用来将文件重命名。
- 文件夹操作相关系统调用
示例代码:
这里有个非常不好的习惯,为了方便,并没有关闭创建或打开文件或目录的文件描述符,即使程序运行结束系统会将这些文件描述符关闭,但是,为了防止文件内容被改动,请手动在程序结束时关闭文件描述符。
#include <sys/stat.h>//mkdir creat
#include <sys/types.h>//mkdir opendir creat
#include <dirent.h>//readdir opendir
#include <unistd.h>//chdir
#include <fcntl.h>//creat
#include <stdio.h>
int main()
{
DIR* dirp;
struct dirent *direntp;
const char *dir_name="dir";
const char *file_name1="test.txt";
const char *file_name2="file.txt";
if(mkdir(dir_name,0755))
{
printf("mkdir error\n");
return -1;
}
if(chdir(dir_name))
{
printf("chdir error\n");
return -1;
}
if(creat(file_name1,0644)<0)
{
printf("create file %s error\n",file_name1);
return 1;
}
if(creat(file_name2,0644)<0)
{
printf("create file %s error\n",file_name2);
return -1;
}
if(mkdir(dir_name,0755))
{
printf("mkdir error\n");
return -1;
}
if(chdir("../"))
{
printf("chdir error\n");
return -1;
}
if((dirp=opendir(dir_name))==NULL)
{
printf("opendir error\n");
return -1;
}
while((direntp=readdir(dirp))!=NULL)
{
printf("%s\n",direntp->d_name);
}
return 0;
}
编译运行结果:
root@Ubuntu-14:/home/zhanghang/github/My_code/APUE/file_IO# ./a.out
test.txt
file.txt
.
dir
..
root@Ubuntu-14:/home/zhanghang/github/My_code/APUE/file_IO# cd dir
root@Ubuntu-14:/home/zhanghang/github/My_code/APUE/file_IO/dir# ls -a
. .. dir file.txt test.txt