Linux下文件I/O系统调用

目录

    • 1.文件扩展名与文件描述符
    • 2.文件I/O操作
      • 程序编写
    • 3.文件I/O操作函数
          • open()系统调用
          • write()系统调用
          • read()系统调用
          • close()系统调用
          • create()系统调用
          • lseek()系统调用
          • dup() 和 dup2()系统调用
          • stat()和fstat()系统调用
          • access()系统调用
          • unlink()系统调用
          • rename()系统调用

1.文件扩展名与文件描述符

文件扩展名

在Linux中,扩展名对Linux内核没有实际意义,但是可以用来人为区分不同的文件,方便用户使用。
◆.tar, .tar.gz, .tgz, .zip, .tar.bz表示压缩文件,创建命令为tar, gzip, unzip等
◆.sh 文件表示shell脚本文件
◆.pl 表示perl语言文件
◆.py 表示python语言文件
◆.conf 表示系统服务的配置文件
◆.c 表示C文件
◆.h 头文件
◆.cpp 表示C++源文件
◆.so 表示动态库文件
◆.a 表示静态库文件

文件描述符
文件描述符(file descriptor, fd)是Linux内核为了高效管理已被打开的文件所创建的索引,其是一个非负整数(通常是小整数),用于指代被打开的文件,所有执行I/O操作的系统调用都通过文件描述符。程序在开始运行时,系统会自动打开三个文件描述符,0是标准输入,1是标准输出,2是标准错误。POSIX标准要求每次打开文件时(含socket)必须使用当前进程中最小可用的文件描述符号码,因此第一次打开的文件描述符一定是3.

文件描述符 用途 POSIX文件描述符 标准I/O文件流
0 标准输入 STDIN_FILENO stdin
1 标准输出 STDOUT_FILENO stdout
2 标准出错 STDERR_FILENO stderr

2.文件I/O操作

程序编写

以下是我学习文件I/O系统调用所写代码

#include        //前6行是在程序中调用函数的头文件包含,具体要包含哪些头文件我们都可以通过man手册来查找;
#include 
#include 
#include 
#include 
#include 

#define  BUFSIZE 1024                                            //定义了两个宏,其中MSG_STR是我们希望写入到文件中的内容,
#define  MSG_STR  "This file is used to learn file IO calls\n"   //他是一个常量会保存到程序的只读数据段中(Rodata);
int main(int argc,char **argv)
{
     
    
     int    fd = -1; // 定义了文件描述符fd, 他应该是一个非负的整数,这里我们将其初始化为-1,这是设置默认让函数的返回值出
                     // 错,如果在接下来的代码中没有对其进行修改就是用的话就会程序抛错;

     int    rv = -1;  // 定义一个变量rv(return value)用来保存函数的返回值;

     char   buf[BUFSIZE]; //定义了一个buffer,用来存放从文件中读到的数据,因为数据是一个一个字节的,
                          // 所以我们一般都是定义成char类型的数组

     fd=open("IO_learn.txt",O_RDWR|O_CREAT|O_APPEND, 0666);    
     if(fd<0)
     {
        
       printf("open file filure:%s\n", strerror(errno));
     }   //调用 open()系统调用并返回一个文件描述符保存在fd中,如果fd<0则说明文件打开失败了,
         //通过perror()函数可以打印系统调用出错的具体原因



     printf("Open file returned file descriptor [%d]\n",fd);
     //调试打印一下fd返回的文件描述符,如果程序运行异常,我们经常会在程序中加
     //入printf来调试错误,这是程序出错调试最常用的方法
    
    
     if((rv=`write(fd, MSG_STR, sizeof(MSG_STR)`))<0)
     {
        
       printf("write file %d bytes filure:%s\n",rv, strerror(errno));
       goto clean;
    
     }   //调用write()系统调用将字符串“”This file is used to learn file IO calls\n”的内容写入到文件中去,
         //函数调用如果出错则返回-1,这时我们打印相应的错误原因;




memset(buf,0,sizeof(buf));//这里我们将buf初始化为0,因为buf内存空间是在栈中,系统自动分配随机值,
                         //如果不初始化的话,后面程序运行时可能会出现乱码
lseek(fd, 0, SEEK_SET);//调用lseek()系统调用将文件偏移量设置到了文件开始的第一个字节上


     if((rv=read(fd, buf, sizeof(buf)))<0)
     {
        
         printf("Read data from file filure:%s",strerror(errno));
        goto clean;
     }//这里我们调用read()系统调用,从文件中读取内容放入到buf中去,如果出错则打印出错的信息

     printf("Read %d bytes data from file: %s\n",rv, buf);//调试打印一下从文件中读到多少个字节的数据及其内容;
  
clean:          //这里定义了一个goto的标号cleanup,并在下一行调用系统调用close()来关闭打开的文件描述符。
     close(fd);
     return  0;  

}

运行程序
Linux下文件I/O系统调用_第1张图片
这里因为我操作的文件本身就存在并且里面有内容“hello world”所以我要写入的内容会跟在原本内容后面。这里的181是因为我多次执行程序后文件被不断写入内容所递增的。

这里介绍两个错误处理函数perror()和strerror()。

void perror(const char *s)
char *strerror(int errno);

perror()的使用方法非常简单,只需要一个字符串参数s即可。他会将错误的原因打印到标准输出上,其内容是字符串s后面紧跟 冒号和字符串形式的错误原因。需要注意的是字符串s 里不要加换行符,因为错误原因里面会带换行。strerror(),它用来将整形类型的错误原因errno装换成相应的字符串形式,这样在任何使用字符串的地方我们都可以使用它。所以在今后的编程中,我们更多的是使用strerror()而不是perror()。下面一段代码示例帮助理解。

int main(int argc, char **argv)
{
     
 char *file_name = "/test.txt"; //根目录下并不存在该文件,在open()打开时会失败
 int fd=-1;
 
 fd=open(file_name, O_RDONLY, 066);
 if( fd < 0 )
 {
     
 perror("Open file failure"); 
 printf("Open file %s failure: %s\n", file_name, strerror(errno)); 
 return 0;
 }
 close(fd);
}

运行程序
Linux下文件I/O系统调用_第2张图片

3.文件I/O操作函数

—————————————————————————————————

open()系统调用
int open(const char *pathname, int flags, mode_t mode);

open()系统调用用来打开一个文件,并返回一个文件描述(filedescription), 并且该文件描述符是当前进程最小、未使用的文件描述符数值。
参数: path: 要打开的文件、设备的路径
     oflag: 由多个选项进行“或”运算构造oflag参数 。
必选: O_RDONLY (只读)、 O_WRONLY(只写)、 O_RDWR(读写)
可选: O_APPEND 每次写时都追加到文件的尾端。
   O_CREAT 文件不存在则创建它,使用该选项需要第三个参数mode
   O_TRUNC 如果文件存在,而且为只写或读写成功打开,则将其长度截取为0,如果在想在一个有内容的文件中写入,此项必不能选;
   O_NONBLOCK 如果path是一个FIFO、块设备、字符特殊文件则此选项为文件的本次打开和后续的I/O操作设置非阻塞模式方式。
   O_EXEC、O_SEARCH、O_CLOEXEC、O_NOCTTY…
mode: oflag带O_CREAT选项时可以用来创建文件,这时必须带该参数用来指定创建文件的权限模式,如066。 否则不需要。

下面是示例代码

int fd; 
 fd = open(“file.txt”, O_RDWR|O_CREAT|O_TRUNC, 0666); 
 fd = open(“file.txt”, O_WRONLY|O_APPEND);

—————————————————————————————————

write()系统调用
ssize_t write(int fd, const void *buf, size_t nbytes);

write()函数用来往打开的文件描述符fd指向的文件中写入buf指向的数据,其中nbytes指定要写入的数据大小。如果返回值<0则说明写入出错,譬如尝试往一个只读的文件中写入则会抛错,错误的原因系统会保存到errno变量去。如果>0则为实际写入的数据大小。
示例代码:

write(fd, hello world, sizeof(hello world))

—————————————————————————————————

read()系统调用
ssize_t read(int fd, void *buf, size_t nbytes);

read()函数用来从打开的文件描述符对应的文件中读取数据放到buf指向的内存空间中去,最多不要超过nbytes个字节,这里的nbytes一般是buf剩余的空间大小。如read成功,则返回实际读到的字节数(由nbytes或读到文件尾决定,其中EOF宏用来判断是否到了文件尾),如果返回值小于0则表示出错,如尝试读一个没有权限读的文件时就会抛错。

close()系统调用

—————————————————————————————————

int close(int fd);

该函数用来关闭一个打开的文件描述符,关闭一个文件时还会释放该进程加在该文件上的所有记录锁。当一个进程终止时,内核将会自动关闭它所有打开的文件。
—————————————————————————————————

create()系统调用
int creat(const char *path, mode_t mode);

此函数用来创建一个新文件并返回其fd。它等价于 open(path, O_WRONLY|O_CREAT|O_TRUNC, mode);
示例代码:

int fd; 
fd=creat(“text.txt”, 0644);

—————————————————————————————————

lseek()系统调用
off_t lseek(int fd, off_t offset, int whence);

我们在从文件里读出内容,或往文件写如内容的时候都有一个起始地址,这个起始地址就是当前文件偏移量,当我们对文件进行读写的时候都会使文件偏移量往后偏移。这点就类似于我们打开记事本开始编辑文本时的光标,我们读或写入时从光标所在位置开始读写,每读写一个字节都会使光标往后偏移。通过lseek()这个函数我们可以调整文件偏移量的地址。其中 whence 可以是以下三个值

whence 位置
SEEK_SET 文件头
SEEK_CUR 当前位置
SEEK_END 文件尾

而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对应的文件。这两个系统调用经常用在标准输入、标准输出、标准出错重定向。
dup()返回的新文件描述符一定是当前可用文件描述符中的最小数值;
dup2可以用fd2参数来指定新的文件描述符。如果fd2已经打开,则先关闭。如fd等于fd2, 则dup2返回fd2, 而不关闭它。

—————————————————————————————————

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 */
 };

示例代码:

int main (int argc, char **argv)
{
     
 struct stat stbuf;
 
 stat("stat.c", &stbuf);
 printf("File Mode: %o Real Size: %luB, Space Size: %luB\n", stbuf.st_mode, stbuf.st_size,
stbuf.st_blksize);
 
 return 0;
}

运行结果:

File Mode: 100664 Real Size: 318B, Space Size: 4096B

—————————————————————————————————

access()系统调用
int access(const char *path, int mode);

access()可以用来测试文件是否存在或测试其权限位,其中第一个参数path是相应的文件路径名,第二个参数是要测试的模式。
其中mode说明如下:

模式 说明
W_OK 测试读许可权
R_OK 测试写许可权
X_OK 测试执行许可权
F_OK 测试文件是否存在

—————————————————————————————————

unlink()系统调用

该系统调用可以用来删除文件,其本质是让文件的链接记数自减。调用该函数将path指定的文件的链接数减1,如果对该文件还有其他链接存在,则仍可以通过其他链接访问该文件的数据。只有当链接记数达到0时,该文件的内容才可被删除。如果有进程打开了该文件,其内容也不能被删除。关闭一个文件时,内核首先检查打开该文件的进程个数,如果这个记数达到0,内核再去检查它的链接记数,如果记数也是0,那么就删除该文件内容。
—————————————————————————————————

rename()系统调用
int rename(const char *oldname, const char *newname);

该系统调用用来将文件重命名。

你可能感兴趣的:(Linux下文件I/O系统调用)