健康等于财富 发表于 2024-1-11 18:53:57

APUE-文件I/O

库函数和系统调用

库函数调用系统调用在所有的ANSI C编译器中,C库函数都是相同的各个操作系统的系统调用是不同的,这导致程序不可移植它调用库函数中的一段程序(或函数)它调用系统内核的服务与用户程序相联系在内核地址空间执行它的运行时间属于“用户时间”运行时间属于“系统时间”属于过程调用,调用开销较小需要在用户控件和内核 上下文环境切换,开销较大在C函数库libc中大约有300个函数在UNIX中大约有90个系统调用典型的C函数库:printf、fopen、fread、malloc典型的系统调用:write、open、read、sbrk、fork文件I/O系统调用

文件和文件描述符

文件扩展名

​                在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 表示静态库文件
文件类型

​                Linux系统中把一切都看做文件,Linux有7中类型文件:普通文件-、目录(dierectory)文件、符号(link)链接、字符(character)设备文件、块(block)设备文件、管道(pipe)文件、套接字(socket)文件。其中文件、目录、符号链接会占用磁盘空间来存储,而块设备、字符设备、套接字、管道是伪文件,并不占用磁盘空间。
​                通过ls -l 命令查看文件类型
文件类型标识文件类型-普通文件d目录文件l符号链接c字符设备b块设备p管道s套接字socket文件描述符

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

程序编写

/*头文件包含,可以通过man手册查找*/
#include <stdio.h>
#include <errno.h>
#include <unistd.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>

/*MSG_STR是写到文件中的内容,是一个常量,会保存到程序的只读数据段中*/
#define BUFSIZE 1024
#define MSG_STR "Hello World\n"

int main(int argc,char *argv[])
{
      int fd = -1;        //文件描述符,正常应该为非负的整数
      int rv = -1;        //返回值return value
      char buf;        //存放从文件中读到的数据

      fd = open("test.txt",O_RDWR|O_CREAT|O_TRUNC,0666);        //调用open()系统调用并返回一个文件描述符保存在fd中
      if(fd < 0)
      {
            /*perror()函数可以打印出错的原因,但是不能处理要进行格式化控制的输出或写入日志文件 */      
                perror("Open/Creat file test.txt failure");      
                return 0;
      }
      printf("Open file returned file descriptor [%d]\n",fd);
      /* write(写到哪里,写什么,写的大小)
           *用strlen是因为MSG_STR是宏定义,宏定义是地址*/
      if((rv = write(fd,MSG_STR,strlen(MSG_STR))) < 0)
      {
            /*strerror()函数可以将整型类型的出错原因errno转换成相应的字符串形式*/
                printf("Write %d bytes into file failure: %s\n",rv,strerror(errno));
                goto cleanup;        //在做错误处理时一般会用goto,做集中的错误处理
      }
   
    /*test.txt里有成功写入,但总是Read 0 bytes data from file,这是因为在Hello World写入后,文件偏移量(类似光标)位于World的后方,读或写入时从光标所在位置开始读写,所以读不到值,通过lseek()系统调用将文件偏移量设置到第一个字节后,Read 12 bytes data from file: Hello World
*/
    //lseek(fd, 0, SEEK_SET);

    /*buf在栈中,栈中的数据是随机数,在使用buf时不对buf进行初始化,会导致后面打印出的是个随机数。因此在往buf中写内容前要先用memset将buf中的内容清掉*/
      memset(buf,0,sizeof(buf));       
      if( (rv=read(fd,buf,sizeof(buf))) < 0 )
      {
                printf("Read data from file failure: %s\n",strerror(errno));
                goto cleanup;
      }

      printf("Read %d bytes data from file: %s\n",rv,buf);

cleanup:
      close(fd);

      /*非main函数return只会导致函数退出
         *main函数的return会调用exit(),导致整个进程中止
         *同理,其他非main函数调用exit()一样会导致整个进程中止
         *程序结束后,可以通过"echo $?"命令查看返回值*/
      return 0;
}strlen()和sizeof()的区别:

[*]sizeof() 是一个运算符,而 strlen() 是一个函数。
[*]sizeof() 计算的是变量或类型所占用的内存字节数,而 strlen() 计算的是字符串中字符的个数。
[*]sizeof() 可以用于任何类型的数据,而 strlen() 只能用于以空字符 '\0' 结尾的字符串。
[*]sizeof() 计算字符串的长度,包含末尾的 '\0',strlen() 计算字符串的长度,不包含字符串末尾的 '\0'。
程序编译运行

li@ubuntu22:~/apue$ gcc file_io.c -o file_io
li@ubuntu22:~/apue$ ./file_io
Open file returned file descriptor
Read 0 bytes data from file:
li@ubuntu22:~/apue$ ./file_io
Open file returned file descriptor         #再运行一次还是3,描述符是当前进程最小可用的,第二次运行是新的进程
Read 0 bytes data from file:
li@ubuntu22:~/apue$ cat test.txt #通过cat test. txt命令查看文件内容验证 "Hello World"字符串确实被wirte()系统调用写入到了文件中
Hello World文件I/O操作函数

open()系统调用

int open(const char *path, int :flag, ... /*mode_t mode*/);open()系统调用用来打开一个文件, 并返回一个文件描述符(file description), 并且该文件描述符是当前进程,最小、未使用的文件描述符数值。
参数: path: 要打开的文件、设备的路径
flag: 由多个选项进行 “或”运算构造flag参数。
​                        必选: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选项时可以用来创建文件, 这时必须带该参数用来指定创建文件的权限模式,如0666。否则不需要。使用示例代码:int fd;
fd = open("text. txt", O_RDWR|O_CREAT|O_TRUNC,
0666);        //O_RDWR文件打开可以通过文件描述符可读可写,O_CREAT文件不存在则创建,O_TRUNC如果文件中有内容先把文件清掉
fd =open("text. txt", O_WRONLY|O_APPEND);        //O_WRONLY只写,O_APPEND追加到文件尾,如果文件不存在则返回-1creat()系统调用

/*用来创建一个新文件并返回其fd,等价于open(path, O_WRONLY|O_CREAT|O_TRUNC,mode);*/
int creat(const char *path,mode_t mode);close()系统调用

/*用来关闭一个打开的文件描述符*/
int close(int fd);write()系统调用

/*用来往打开的文件描述符fd指向的文件中写入buf指向的数据,nbytes指定要写入的数据大小*/
size_t write(int fd,const void *buf,size_t nbytes);read()系统调用

/*用来从打开的文件描述符对应的文件中读取数据放到buf指向的内存空间中去,最多不要超过nbytes个字节,nbytes一般是buf剩余的空间大小*/
size_t read(int fd,void *buf,size_t nbytes);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对应的文件。这两个系统调用经常用在标准输入、标准输出、标准出错重定向。
dup0返回的新文件描述符一定是当前可用文件描述符中的最小数值
dup2可以用fd2参数来指定新的文件描述符。如果fd2已经打开,则先关闭。如fd等于fd2,则dup2返回fd2,而不关闭它。
#include <stdio.h>
#include <errno.h>
#include <unistd.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>

int main(int argc,char *argv[])
{
      int fd = -1;

      fd = open("std.txt",O_RDWR|O_CREAT|O_TRUNC,0666);
      if( fd < 0 )
      {
                printf("Open file failure: %s\n",strerror(errno));
                return 0;
      }

      close(0);        //用dup()先close()
      close(1);
      close(2);
                dup(fd);    //0 标准输入
                dup(fd);    //1 标准输出
                dup(fd);    //2 标准错误输出

//    dup2(fd, STDIN_FILENO);   // 标准输入重定向到fd
//    dup2(fd, STDOUT_FILENO);    // 标准输出重定向到fd
//    dup2(fd, STDERR_FILENO);    // 标准错误输出重定向到fd

      printf("fd=%d\n",fd);

      close(fd);
      return 0;
}li@ubuntu22:~/apue$ vim redirect_stdio.c
li@ubuntu22:~/apue$ gcc redirect_stdio.c -o redirect_stdio
li@ubuntu22:~/apue$ ./redirect_stdio
li@ubuntu22:~/apue$ echo $?
0
li@ubuntu22:~/apue$ ls
file_iofile_io.credirect_stdioredirect_stdio.cstd.txttest.txt
li@ubuntu22:~/apue$ cat std.txt
fd=3stat()和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
    ino_t st_ino;         // 文件的 inode 号
    mode_t st_mode;       // 文件类型和权限
    nlink_t st_nlink;   // 文件的硬链接数目
    uid_t st_uid;         // 文件所有者的用户 ID
    gid_t st_gid;         // 文件所有者的组 ID
    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;      // 最后一次更改时间(修改文件的权限或者所有者)
};#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>

int main(int argc,char **argv)
{
      struct stat stbuf;

      stat("stat.c",&stbuf);
   
      /*stbuf是一个指针,在栈区,它指向的地址是一个随机地址(野指针),使用指针一定要先初始化:
      1、 使用malloc
              struct stat *stbuf;
              stbuf = malloc(sizeof(struct stat))
      2、定义一个变量stbuf,传&stbuf
              struct stat stbuf;
              stat("stat.c",&stbuf);
      */
      //struct stat *stbuf;
      //stat("stat.c",stbuf);
   
   
            /*fstat()要先用open()后使用*/
//            int fd = -1;
//            fd = open("stat.c",O_RSONLY);
//            fstat(fd,stbuf);
   
   
      printf("File Mode: %o Real Size: %luB,Space Size: %luB\n",stbuf.st_mode,stbuf.st_size,stbuf.st_blksize);

      return 0;
}li@ubuntu22:~/apue$ vim stat.c
li@ubuntu22:~/apue$ gcc stat.c -o stat
li@ubuntu22:~/apue$ ./stat
File Mode: 100664 Real Size: 283B,Space Size: 4096B
li@ubuntu22:~/apue$access()系统调用

int access(const char *path,int mode)用来测试文件是否存在或测试其权限位,其中第一个参数path是相应的文件路径名,第二个参数是要测试的模式。
mode说明:
模式说明R_OK测试读许可权W_OK测试写许可权X_OK测试执行许可权F_OK测试文件是否存在#include <stdio.h>
#include <unistd.h>

#define TEST_FILE "access.c"

int main(int argc,char *argv[])
{
            //测试文件是否存在
      if(access(TEST_FILE,F_OK) != 0)
      {
                printf("File %s not exist!\n",TEST_FILE);
                return 0;
      }

      printf("File %s not exist!\n",TEST_FILE);

            //测试读许可权
      if(access(TEST_FILE,R_OK)==0)
      {
                printf("READ OK\n");
      }
                //测试写许可权
      if(access(TEST_FILE,W_OK)==0)
      {
                printf("WRITE OK\n");
      }
                //测试执行许可权
      if(access(TEST_FILE,X_OK)==0)
      {
                printf("EXEC OK\n");
      }

      return 0;
}li@ubuntu22:~/apue$ ./access
File access.c not exist!
READ OK
WRITE OKunlink()系统调用

用来删除文件,其本质是让文件的链接记数自减,当链接记数减为0时,文件会被自动删除。(Linux下rm命令其实就是调用了unlink()系统调用)
rename()系统调用

将文件重命名
文件夹操作相关系统调用

函数原型函数说明int mkdir(const char *pathname,mode_t mode)创建文件夹int rmdir(const char *pathname)删除文件夹(文件夹必须为空)DIR *opendir(const char *pathname)打开文件夹struct dirent *readdir(DIR *dp)读文件夹int closedir(DIR *dp)关闭文件夹int chdir(const char *pathname)改变工作目录readdir()系统调用的struct dirent:
struct dirent
{
  long d_ino; /* inode number 索引节点号 */
    off_t d_off; /* offset to this dirent 在目录文件中的偏移 */
    unsigned short d_reclen; /* length of this d_name 文件名长 */
    unsigned char d_type; /* the type of d_name 文件类型 */
    char d_name ; /* file name (null-terminated) 文件名,最长255字符 */
}#include <stdio.h>
#include <errno.h>
#include <unistd.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>

#define TEST_DIR "dir"

int main(int argc,char *argv[])
{
      int rv = 0;
      int fd1 = -1;
      int fd2 = -1;
      DIR *dirp = NULL;
      struct dirent *direntp = NULL;

            /* 创建文件夹,文件权限755 */
      if( mkdir(TEST_DIR,0755) < 0 )
      {
                printf("creat directory '%s' failure: %s\n",TEST_DIR,strerror(errno));
                return -1;
      }

            /* 更改当前工作路径到文件夹dir下 */
      if( chdir(TEST_DIR) < 0 )
      {
                printf("change directory '%s' failure: %s\n",TEST_DIR,strerror(errno));
                rv = -2;
                goto cleanup;
      }
   
            /* 在dir文件夹下 创建普通文本文件file1.txt,并设置其权限位为644 */
                if( (fd1 = creat("file1.txt",0644)) < 0 )
      {
                printf("creat file1.txt failure: %s\n",strerror(errno));
                rv = -3;
                goto cleanup;
      }

            /* 在dir文件夹下 创建普通文本文件file2.txt,并设置其权限位为644 */
      if( (fd1 = creat("file2.txt",0644)) < 0 )
      {
                printf("creat file2.txt failure: %s\n",strerror(errno));
                rv = -4;
                goto cleanup;
      }

                  /* 更改当前工作路径到父目录去 */
      if( chdir("../") < 0 )
      {
                printf("change directory to '%s' failure: %s\n",TEST_DIR,strerror(errno));
                rv = -5;
                goto cleanup;
      }

            /* 打开dir文件夹 */
      if ((dirp = opendir(TEST_DIR)) == NULL)
      {
                printf("opendir %s error: %s\n", TEST_DIR, strerror(errno));
                rv = -6;
                goto cleanup;
      }
   
            /* 列出dir里面的所有文件和文件夹,一个文件夹下至少要有.和..两个文件,从文件夹下读时,每个文件都是一个dirent*/
                while((direntp = readdir(dirp)) != NULL)
      {
                printf("find file: %s\n",direntp->d_name);
      }

            /* 关闭所有打开的文件夹 */
      closedir(dirp);
      return rv;

cleanup:
      if(fd1 >= 0)
      {
                close(fd1);
      }

      if(fd2 >= 0)
      {
                close(fd2);
      }
}li@ubuntu22:~/apue$ vim dir.c
li@ubuntu22:~/apue$ gcc dir.c -o dir
li@ubuntu22:~/apue$ ./dir
find file: file1.txt
find file: ..
find file: file2.txt
find file: .
li@ubuntu22:~/apue$ ls directory -la
total 8
drwxr-xr-x 2 li li 40969月 24 21:16 .
drwxrwxr-x 3 li li 40969月 24 21:16 ..
-rw-r--r-- 1 li li    09月 24 21:16 file1.txt
-rw-r--r-- 1 li li    09月 24 21:16 file2.txt
来源:https://www.cnblogs.com/LiBlog--/p/17958787
免责声明:由于采集信息均来自互联网,如果侵犯了您的权益,请联系我们【E-Mail:cb@itdo.tech】 我们会及时删除侵权内容,谢谢合作!
页: [1]
查看完整版本: APUE-文件I/O