Linux下的文件IO

大概是一年前学习的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
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值