第八九章,知识完整性总结

发布时间 2023-09-30 21:52:52作者: 20211106隋吉达

第八章-使用系统调用进行文件操作

目录
1.系统调用
  1. 操作系统中进程以内核模式和用户模式运行,系统调用可以暂时让处于用户模式的进程拥有内核模式的高权限,以便拥有必要的操作权限。
2.系统调用手册
  1. 可以使用 man 2 stat等命令来查看操作说明: man 2 stat: 查看stat() ,fstat() ,lstat()函数的说明 man 2 open: 查看open()函数的说明 man 2 read: 查看read()函数的说明
3.使用系统调用进行文件操作

​ 系统调用必须由程序发出,其最终用法像普通函数一样。每个系统调用都是一个库函数,它汇集系统调用参数,并最终向操作系统内核发出一个系统调用,可实现用户模式进程向内核模式的转变

下面是实现系统调用的一般函数格式: int syscall(int a, int b, int c, int d);

各个参数的含义: a: 系统调用编号 b: 对应内核函数的参数 c: 对应内核函数的参数 d: 对应内核函数的参数

​ 当进程结束执行内核函数时,会返回到用户模式,并得到所需的结果。返回值≥0表示成功,-1表示失败。如果失败,errno变量(在errno.h中)会记录错误编号,它们会被映射到描述错误原因的字符串。

4.系统调用指令
  1. 简单的系统调用 access: 检查对某个文件的权限

    int access(char *pathname, int mode);
    

chdir: 更改目录

   int chdir(const char *path);

chmod:更改某个文件的权限

   int chmod(char *path, mode_t mode);

chown:更改文件所有人

   int chown(char *name,int uid,int gid),

chroot:将(逻辑)根目录更改为路径名

   int chroot(char *pathname):

getewd:获取CWD的绝对路径名

   char *getcwd(char *buf, int aize):

mkdir:创建目录

   int mkdir(char *pathname,mode_t mode);

rmdir:移除目录(必须为空)

   int rmdir(char *pathname);

link:将新文件名硬链接到旧文件名

   int 1ink(char *o1dpath,char *newpath);

umlink:减少文件的链接数;如果链接数达到0,则删除文件

   int unlink(char *pathname);

symlink:为文件创建一个符号链接

   int symlink(char o1dpath, charnewpath);

rename:更改文件名称

   int rename (char *oldpath, char *newpath);

utime:更改文件的访问和修改时间

   int utime(char *pathname, struct utimebuf *time) 以下系统调用需要超级用户权限。

mount:将文件系统添加到挂载点目录上

   int mount(char *specialfile, char *mountDir);

umount:分离挂载的文件系统

   int umount(char *dix);

mknod:创建特殊文件

   int mknod(char *path,int mode, int device);
  1. 常用的系统调用 stat 获取文件状态信息

    int stat(char *filename ,struct stat *buf)
    int fstat(char filedes ,struct stat *buf)
    int lstat(char *filename ,struct stat *buf)
    

open:打开一个文件进行读、写、追加

   int open(char *file, int flags,int mode)

close:关闭打开的文件描述符

   int close(int fd)

read:读取打开的文件描述符

   int read(int fd, char buf[ 1, int count)

write:写入打开的文件描述符

   int write(int fd, char buf[ ], int count)

lseek:重新定位文件描述符的读/写偏移量

   int 1seek(int fd, int offset, int whence)

dup:将文件描述符复制到可用的最小描述符编号中

   int dup(int oldfd);

dup2:将oldfd复制到newfd中,如果newfd已打开,先将其关闭

   int dup2(int oldfd, int newfd)

link:将新文件硬链接到旧文件

   int link(char *oldPath, char *newPath)

unlink:取消某个文件的链接;如果文件链接数为0,则删除文件

   int unlink(char *pathname);

symlink:创建一个符号链接

   int symlink(char *target, char *newpath)

readlink:读取符号链接文件的内容

   int readlink(char *path, char *buf, int bufsize)

umask:设置文件创建掩码;文件权限为(mask&~umask)

   int umask(int umask)

​ 这些常用的文件操作系统调用都在第十章的文件操作中有所实践。

5.打印文件实践

对于一个文件,有时需要打印它出来,而这个程序就可以实现这一功能: /---20211106---/ #include <stdio.h> #include <stdlib.h> #include <fcntl.h> #include <unistd.h> #define BLKSIZE 4096 int main(int argc , char *argv[]) { int fd , i , m , n; char buf[BLKSIZE] , dummy; fd = 0; if (argc > 1){ fd = open(argv[1] , O_RDONLY); if (fd < 0) exit(1); } while (n = read(fd , buf , BLKSIZE)){ m = write(1 , buf , n); } }

它可以将文件内容输出,当没有指定文件时,它就会输出输入缓冲区里内容: ./a.out :输出输入缓冲区内容 ./a.out finame :输出文件内容

I/O库函数
  1. I/O库函数与系统调用系统调用是文件操作的基础,但是它只支持对于数据块的读和写操作,并不能满足用户对于数据的结构化操作的需求,而I/O库函数就是为了满足该需求而生的,I/O库函数的使用提高了对数据的处理效率。

    系统函数:
    open(),用于创建一个新的文件描述符;
    read(),读取文件,从文件描述符 fildes 相关联的文件里读入 nbytes 个字节的数据,并把它们放到数据区 buffer 中;
    write(),把缓冲区 buffer 的前 nbytes 个字节写入与文件描述符 fildes 关联的文件中;
    lseek(),用于改变读写操作时的位置指针;
    close(),终止文件描述符 fildes 与其对应文件之间的关联。

    I/O库函数:
    

    fopen(),主要用于对文件和终端的输入输出;
    fread(),主要作用是从一个文件流里读取数据,数据从stream读到由ptr指定的数据缓冲区里面;
    fwirte(),从stream获取数据记录写到ptr中,返回值是成功写入的记录个数;
    fseek(),在文件流里面为下一次读写指定位置;
    fclose(),关闭指定的文流stream,使所有未写出的内容全部写出。
    可以看出来,二者之间的函数功能基本可以一一对应,但是在系统调用中,使用了整数型变量文件描述符fd,而在I/O库函数中则使用了文件流指针fp;系统调用的buffer最多包含4KB的字符,并且一般使用while循环一次一个字节的写入数据,效率十分低下。而I/O库函数可以使用fgetc(fp)将文件流中的所有数据读取,十分高效。

  2. I/O库函数算法

    1. 算法之fread()
      函数原型:
     #include <stdio.h>
    
     size_t fread(void *ptr, size_t size, size_t nitems, FILE *stream);
    
    
     ptr:数据读到ptr指定的缓冲区里面。
    
     size:每个数据记录的长度(类似与char,int,long,float之类,指代每次读取块的大小)
    
     nitems:传输的记录个数
    
     stream:指定要读取的数据缓冲区
    
     返回值:读到缓冲区的记录个数(非字节),如果读到文件尾,其返回值可能小于nitems,甚至可以是0(读取文件为空)。
    
    1. 算法之fwrite()

      函数原型:

      include <stdio.h>

      size_t fwrite(const void *ptr, size_t size, size_t items, FILE *stream);

      函数中的参数和fread()类似,可以一起看。

    2. 算法之fclose()

      函数原型:

      include <stdio.h>

      int fclose(FILE *stream);

    函数参数:
    stream:指定要关闭的文件流stream

     返回值:如果成功返回0,失败返回EOF,同时会向全局变量errorn写入错误信息码。
    
             文件写入与读取代码:
    

其它的算法见:常用标准I/O库函数总结

I/O库模式
在fopen()函数中,有模式参数"r","w"和"a",分别代表了读、写和追加三种模式。每个模式字符串可以添加一个+号,表示可以同时读写或者追加写入,如果文件不存fopen()函数会创建这个文件。
  1. 字符模式I/O(重点)

    函数原型:

      int fgetc(    File    *fp);
      int unget(    int c ; FILE *fp);
      int fputc(    int c , FILE *fp);
    
    
    这三个函数均是按字符读取文件内容,可以实现对文件的按字符读写(其中fgetc实现按字符读操作,而ungetc和fputc则是按字符写入操作)
    
  2. 行模式I/O(重点) 函数原型:

     char *fgets(char *buf , int size_z , FILE *fp);
    
     int fputs(char *buf , FILE *fp);
    

    fgets()函数从指定的流 fp 读取一行,并把它存储在 buf 所指向的字符串内。当读取 (size_z-1) 个字符时,或者读取到换行符时,或者到达文件末尾时,它会停止。

    参数解析:

    • buf -- 这是指向一个字符数组的指针,该数组存储了要读取的字符串。
    • size_z -- 这是要读取的最大字符数(包括最后的空字符)。通常是使用以 str 传递的数组长度。
    • fp -- 这是指向 FILE 对象的指针,该 FILE 对象标识了要从中读取字符的流。

    fputs()函数则是将 buf 指向的字符串按行输入到 FILE 指针指向的对象文件中去,其参数与fgets()函数类似,可以以它作为参考。

  3. 格式化的I/O 1.格式化输入 scanf() 和 fscanf(),格式化的输入; 函数原型:

    scanf(char *FMT , &items);
    
    fscanf(FILE *fp , char *FMT , &items);
    

    scanf()从标准输入 stdin 读取格式化输入;fscanf()从流 fp 读取格式化输入;

    参数解析:
    二者功能类似,参数可互相参考
    fp -- 指向 FILE 对象的指针, FILE 对象标识了 fp 流;
    FMT -- 表示C字符串,包含了以下各项中的一个或多个:空格字符、非空格字符 和 format 说明符。

format说明符见 :C 库函数 - fscanf() 2.格式化输出 printf()和fprintf(),格式化的输出; 函数原型: printf(char *FMT , &items);

fprintf(FILE *fp , char *FMT , &items); printf()发送格式化输出到标准输出stdout;fprintf()发送格式化输出到流 fp 中; 参数可以参考格式化输入。

  1. 数据类型转换函数

    1.sscanf(buf , FMT , &items);从字符串读取格式化输入;
    2.sprintf(buf , FMT , items);发送格式化输出到 str 所指向的字符串;可以完成ASCII数字和整数之间的转换。
    

sprintf实现字符串整型数转换结果:

PS:可以实现整型与字符串类型转换的两个函数:itoa()(integer to alphanumeric,整型数转字符串)和 atoi()(alphanumeric to integer,字符串转整型数)1、itoa()它可以实现整型数向字符串的类型转变;2、atoi()它可以实现把字符串转换成整型数的操作;函数原型如下: char* itoa(int value ,char *string ,int radix); int atoi(const char *nptr);

其参数如下: itoa(): value: 要转换的整数; string: 转换后的字符串; radix: 转换进制数,如2,8,10,16 进制等; atio(): nptr:要转换的字符串。

  1. 其它的I/O数据库函数
  • fseek() ftell() rewind() :更改文件流中的读/写字节位置;
  • feof() ferr() fileno() :测试文件流状态;
  • fdopen() :用文件描述符打开文件流;
  • freopen() :以新名称重新打开现有流;
  • setbuf() setvbuf() :设置缓冲方案;
  • popen() :创建管道,复刻子进程来调用sh。
  1. 限制混合fread()和fwrite() 当文件流要同时使用 fread()函数和fwrite()函数时,会限制混用这两个函数的调用,要求编程者在二者之间至少要有一个fseek()和ftell()
文件流缓冲

每一个文件流都有一个FILE结构体,它包含了一个内部的缓冲区。想要对文件流进行读写就需要对该缓冲区进行遍历。所以才会有文件的流缓冲。主要有以下三种方式。

  • 无缓冲:标准I/O不对字符进行缓冲处理,即对所有的从非缓冲流中写入或者读取的字符将马上被单独传送到文件中去或者从文件中传输出来。如stder函数;

  • 行缓冲:当在输入和输出遇到换行符时,标准I/O执行I/O操作。如stdout函数;

  • 全缓冲:在填满I/O缓冲区后再进行实际的I/O操作。一般的文件流缓冲方式。

在fopen()创建文件流之后,在对它进行任何操作之前,都可以发送以下函数来选择采用何种缓冲方式: int setvbuf(FILE *stream, char *buf, int mode, int size)

其中参数如下:

  • stream -- 这是指向 FILE 对象的指针,该 FILE 对象标识了一个打开的流。

  • buf -- 这是分配给用户的缓冲。如果设置为 NULL,该函数会自动分配一个指定大小的缓冲。

  • mode -- 这指定了文件缓冲的模式;

  • size --这是缓冲的大小,以字节为单位。

模式必须为以下三者之一:

  • _IOFBF/(F=full)全缓冲:对于输出,数据在缓冲填满时被一次性写入。对于输入,缓冲会在请求输入且缓冲为空时被填充;

  • _IOLBF/(L=line)行缓冲:对于输出,数据在遇到换行符或者在缓冲填满时被写入,具体视情况而定。对于输入,缓冲会在请求输入且缓冲为空时被填充,直到遇到下一个换行符;

  • _IONBF 无缓冲:不使用缓冲。每个 I/O 操作都被即时写入。buffer 和 size 参数被忽略。

变参函数
参数数量可变的函数,比如printf()函数。其基本格式如下:


    int func(int n , int m , ...);