Linux I/O

I/O 输入输出

i:input 输入 o:output 输出

对文件的输入 :输出-》读文件

​ :输入-》写文件

文件存放在磁盘空间,断电不会丢失文件类型(bcd-lsp)

1.b(Block Device File):块设备文件
2.C(Character Device File):字符设备文件
3.d(Directory): 目录文件
4.-:普通文件
5.L(Link):链接文件
6.s(socket):套接字文件:用于网络通信(网络编程)
7.P(pipe):管道文件:用于进程间的通信方式

标准IO和文件IO

image-20250218140533681

标准IO

标准IO是C库中定义的一组用于输入输出的函数接口。不同的操作系统只要移植了C库就可以使用,它是在系统调用之前做了一个二次封装,相当于是间接的进行了系统调用。可移植性强,可以在不同的系统环境下进行使用。

文件IO

内核向上提供的输出输出函数接口,叫做系统调用函数接口。基于内核,内核不同,系统调用函数接口不同,文件IO不同操作系统函数接口不通用。可移植性较差。

标准IO的调用逻辑

if是linux操作系统
{
    调用linux的系统函数(文件io)
}
if是window操作系统
{
    调用windows的系统函数(文件io)
}
if是macos操作系统
{
     调用macos的系统函数(文件io)
}

缓存机制

缓存机制

正常的系统调用-》应用层读写文件-》调用内核层的函数接口-》内核层与硬件层交互-》拿到数据返回给应用层-》每次读写重复操作


带有缓存机制的调用-》应用层读写文件-》调用内核层的函数接口-》内核层与硬件层交互-》拿到数据返回给应用层的缓存区-》每次读写都从缓存区里拿数据

标准IO

标准IO的特点

1.标准IO是C库中提供的一组专门用于输入输出的函数接口
2.标准IO由ANSI C标准定义,不仅在Unix系统上,在很多操作系统上都实现了标准IO,可移植性强
3.标准IO通过缓存机制减少系统调用次数,提高效率
4.标准IO默认打开三个流:标准输入(stdin),标准输出(stdout),标准出错(stderr)。
5.标准I/O库的所有操作都是围绕流(stream)来进行的,在标准I/O中,流用FILE *来描述。

定义

所有的I/O操作仅是简单的从程序移进或者移出,这种字节流,就称为流。

分类

文本流/二进制流

流指针 FILE*

FILE* 是一个指向 FILE 结构体的指针,这个结构体由标准库定义,用于表示一个打开的文件或输入/输出流。

typedef struct _iobuf {
char *_ptr; // 文件输入的下一个位置
int _cnt; // 当前缓冲区的相对位置
char *_base; // 文件初始位置
int _flag; // 文件标志
int _file; // 文件有效性
int _charbuf; // 缓冲区是否可读取
int _bufsiz; // 缓冲区字节数
char *_tmpfname; // 临时文件名
} FILE;

缓存区分类

1.全缓存:基于文件
刷新缓存:
程序正常的退出
遇到(return)
遇到exit退出进程
fclose(关闭流指针)
fflush:强制刷新缓存区
缓存区满
2.行缓存:基于终端-》stdin/stdout
刷新缓存:
程序正常的退出
遇到(return)
遇到exit退出进程
fclose(关闭流指针)
fflush:强制刷新缓存区
缓存区满
\n刷新
3.不缓存:stderr
注意:缓存区只有在使用的时候才会被开辟

计算缓存区大小

image-20250218144201140

fflush函数

#include <stdio.h>
int fflush(FILE *stream);
功能:刷新缓存区
参数:
    stream:流  (NULL:刷新所有流)
返回值:成功0
  失败EOF(-1),更新errno。

用法:fflush(NULL)

标准IO的函数接口

1. 打开文件:fopen,关闭文件:fclose
2. 读/写单个字符:fgetc,fputc
3. 读/写一个字符串:fgets,fputs
4. 读/写一个二进制文件:fread,fwrite
5.移动指针的函数:fseek
fopen打开文件
#include <stdio.h>
FILE *fopen(const char *path, const char *mode);
功能:打开文件

参数:path:打开的文件的路径
      mode:打开方式
打开方式:
r:只读,文件指针定位到文件开头(有文件)
r+:可读可写,文件指针定位到文件开头(有文件)
w:只写,文件不存在创建,存在清空,文件指针定位到文件开头
w+:可读可写,文件不存在创建,存在清空,文件指针定位到文件开头
a: 只写,文件不存在创建,存在追加(到文件末尾)
a+:可读可写,文件不存在创建,存在追加(到文件末尾)
读文件指针定位到文件开头。
返回值:成功-的到文件流指针
        失败:NULL,更新errno

示例

#include <stdio.h>

int main(int argc, char const *argv[])
{
    FILE *fp = fopen("./test.c","w");
    if (fp == NULL)
    {
        perror("fopen err");
        return -1;
    }
    printf("fopen success\n");
    return 0;
}

开多少个文件?(1024)一个文件能够重复打开吗?(可以)

注意:打开文件属于有限资源,使用完记得及时关闭

fclose关闭文件
int fclose(FILE *stream);
功能:关闭文件
参数:
stream:流指针
返回值:成功0,失败-1,更新errno

fclose(fp);
perror
#include <errno.h>
void perror(const char *s);
功能:根据errno值获取错误信息,将信息输出到终端
参数:S:提示内容
返回值:无
fgetc读单个字符
int fgetc(FILE *stream);
 功能:从文件中读一个字符
 参数:
    stream:流指针(从那个文件读)
返回值:成功返回读到字符的ascii值,失败返回或读到文件结尾返回-1.
#include <stdio.h>

int main(int argc, char const *argv[])
{
    FILE *fp1 = fopen("./hello.c","r");
    if (fp1 == NULL)
    {
        perror("fopen err");
        return -1;
    } 
    printf("fopen success\n");
    int ch = fgetc(fp1);
    printf("%c\n",ch);
    fclose(fp1);
    return 0;
}
fputc写单个字符
int fputc(int c, FILE *stream);
功能:向指定的文件中写入一个字符
参数:
    c:要写入字符的ASCII值
    stream:流指针
返回值:写入字符ASCII值
       失败返回:EOF

用上面的fgetc和fputc实现复制的功能,将文件1中的内容读取复制到文件2

#include <stdio.h>
int main(int argc, char const *argv[])
{
    // 打开文件流
    FILE *fp_r = fopen("./test.txt", "r");  // 只读
    FILE *fp_w = fopen("./test1.txt", "w");  // 只写
    if (fp_r == NULL && fp_w == NULL)
    {
        perror("fopen err");
        return -1;
    }
    printf("fopen success\n");
    int ch = 0;
    while ( (ch = fgetc(fp_r)) != -1)
        fputc(ch,fp_w);
    fclose(fp_r);
    fclose(fp_w);
    return 0;
}
fprintf
#include<stdio.h>
 int fprintf(FILE *stream, const char *format, ...);
功能:向指定的文件以指定的格式写入数据
参数: stream :流指针
        format:指定格式
        ...:多个参数
    返回值:输出字符个数
     失败返回:EOF
fgets读字符串
char *fgets(char *s, int size, FILE *stream);
功能:从文件中获取指定长度的字符串
参数: s:字符串存放的首地址
     size:期望获取字符的个数   
             实际读size-1个字符,会自动补'\0',预留一个位置补'\0'.
             文件中不满size-1个,有多少读多少,都会补'\0'.
             当读到'\n',结束,不再读下一行内容,再次调用fgets继续从下一行开始读。
     stream:文件流指针
返回值:
    成功:返回获取成功字符串存放的首地址
    失败或读到文件结尾返回NULL。
#include <stdio.h>
int main(int argc, char const *argv[])
{
    FILE *fp1 = fopen("./test.c","r");
    if (fp1 == NULL)
    {
        perror("fopen err");
        return -1;
    }
    int age = 18;
    printf("fopen success\n");
    char buf[64];
    while (fgets(buf,sizeof(buf),fp1) != NULL)
         printf("buf = %s\n",buf);
    fclose(fp1);
    return 0;
}
fputs写字符串
int fputs(const char *s, FILE *stream);
   功能:向指定文件中输入一串字符
   参数:
         s:输入字符串的首地址
         stream:文件流指针
    返回值:成功返回输出字符个数
    失败返回EOF
#include <stdio.h>
int main(int argc, char const *argv[])
{
    FILE *fp1 = fopen("./test.c","w");
    if (fp1 == NULL)
    {
        perror("fopen err");
        return -1;
    }
    int age = 18;
    printf("fopen success\n");
    char buf[64];
    while (fgets(buf,sizeof(buf),fp1) != NULL)
    {
        fputs(buf,fp1);
    }
    fclose(fp1);
    return 0;
}

文件IO

内核向上提供的输入输出函数接口,叫做系统调用函数接口。基于内核,内核不同,系统调用函数接口不同,文件IO不同操作系统函数接口不通用。可移植性较差。

特点

1.没有缓存机制,每次调用都会引起系统调用。
2.围绕文件描述符进行操作,文件描述符都是非负的整数,依次进行分配。
3.文件IO默认打开三个文件描述符,0(标准输入),1(标准输出),2(标准出错)
4.文件IO可以操作除了目录文件之外的任意类型的文件。

文件IO的函数接口

1.打开文件 open
2.关闭文件 close
3.读文件 read
4. 写文件 write
5. 偏移函数 lseek
open 打开文件
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>

nt open(const char *pathname, int flags);
功能:打开文件
参数:pathname:文件路径名
      flags:打开文件的方式
            O_RDONLY:只读
            O_WRONLY:只写
            O_RDWR:可读可写
            O_CREAT:创建
            O_TRUNC:清 空
            O_APPEND:追加       
fopen() mode open() flags
r O_RDONLY
w O_WRONLY | O_CREAT | O_TRUNC
a O_WRONLY | O_CREAT | O_APPEND
r+ O_RDWR
w+ O_RDWR | O_CREAT | O_TRUNC
a+ O_RDWR | O_CREAT | O_APPEND

三种参数的情况

当文件中存在O_CREAT时,使用函数

int open(const char *pathname, int flags, mode_t mode);

mode:创建文件的权限,实际权限的算法是:mode &~umask

umask:文件权限掩码,值为0002。比如:mode:0666的话,实际的权限为0664

权限掩码可更改;查看权限掩码:umask 修改文件权限掩码:umask 0000

修改后的权限掩码就可以与mode保持一致。

#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>

int main(int argc, char const *argv[])
{
    int fd = open("./cp.c", O_RDONLY);
    if (fd == -1)
    {
        perror("open err");
        return -1;
    }
    printf("fd = %d\n",fd);

    return 0;
}
close关闭文件
#include <unistd.h>
 int close(int fd);
 功能:关闭文件
read读函数
#include <unistd.h>
ssize_t read(int fd, void *buf, size_t count);
功能:读文件
参数:fd:文件描述符
      buf:存放内容的首地址
      count:期待读取字符的个数
返回值:成功实际读到字符的个数,读到文件结尾返回0
        失败返回-1,更新errno

注意:
使用read函数时需要注意:read读文件,一般期待读多少字符就是读多少,不会自动补’\0‘,遇到\n也不会自动停止读取,会继续读下一行的内容。
'\0'需要自己补充,考虑预留一个字节补'\0'

使用技巧:
1.通过返回值作为实际读到的字符个数,后面补‘\0’;
        char buf[32];
        ret = read(fd,buf,31);
        buf[ret] = '\0';
2.每次读内容放到数组之前先清空数组。
清空数组的函数
1. memset
 void *memset(void *s, int c, size_t n);
功能:清空数组
参数:s:清空的数组首地址
      c:一般写0
      n:清空的字节数
2. bzero
void bzero(void *s, size_t n);
功能:清零函数
  参数:
       s:要清空内容的首地址
       n:清空的字节数
   返回值:无
write函数
ssize_t write(int fd, const void *buf, size_t count);
功能:写文件
参数:
    fd:文件描述符
    buf:写内容存放的首地址
    count:期待写字符的个数
返回值:成功实际写字符的个数
    失败返回-1,更新errno

ssize_t ret = read(fd1,buf,64)  
write(fd2,buf,ret)
读多少写多少

利用read和write函数实现cp功能,命令行传参实现

#include <stdio.h>
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
int main(int argc, char const *argv[])
{
    //判错
    if (argc != 3)
    {
        printf("please filename\n");
        return -1;
    }
    //打开两个文件
    int fd_src = open(argv[1],O_RDONLY);
    if (fd_src == -1)
    {
        perror("open err");
        return -1;
    }
    int fd_dest = open(argv[2],O_WRONLY | O_TRUNC | O_CREAT,0666);
    if (fd_dest == -1)
    {
        perror("open err");
        return -1;
    }
    char buf[32];
    ssize_t ret = 0;
    //读取源文件,写入目标文件
    while (ret = read(fd_src,buf,sizeof(buf)))
    {
        write(fd_dest,buf,ret);
    }
    close(fd_src);
    close(fd_dest);

    return 0;
}
lseek函数
 #include <sys/types.h>
 #include <unistd.h>

       off_t lseek(int fd, off_t offset, int whence);
       功能:将文件指针移动到指定位置
       参数:
           fd:文件描述符
           offset:偏移量  +向后 -向前
           whence:相对位置
               SEEK_SET:开头
               SEEK_CUR:当前
               SEEK_END:结尾
    返回值:成功:当前位置(基于文件开头)
            失败:-1,更新errno

 off_t len = lseek(fd,0,SEEK_END)

标准IO和文件IO的区别

标准IO 文件IO
概念 C库定义的一组用于输入输出的函数 系统中定义的一组用于输入输出的函数
特点 1)有缓冲机制2)围绕流进行操作,FILE *3)默认打开了三个流:stdout/stdin/stderr 1)无缓冲机制2)围绕文件描述符操作,非负整数3)默认打开了三个文件描述符:0/1/24)可以操作任意类型的文件
函数 打开文件:fopen关闭文件:fclose读写操作:fgetc fputc fputs fgets fread fwrite 定位操作:fseek rewind ftell时间:time localtime其他: fflush fprintf perror 打开文件:open关闭文件:close读写操作:read write定位操作:lseek

目录操作函数

1. 打开目录:opendir
2. 关闭目录:closedir
3. 读目录:readdir
4. 修改文件路径;chdir
5. 获取文件属性:stat

opendir打开目录

1.opendir  打开目录
  #include <sys/types.h>
  #include <dirent.h>
  DIR *opendir(const char *name);
  功能:打开目录文件
  参数:name:文件名
 返回值:成功返回目录流指针,失败NULL,更新errno
#include <stdio.h>
#include <sys/types.h>
#include <dirent.h>
int main(int argc, char const *argv[])
{
    DIR *dirp = opendir("./");
    if (dirp == NULL)
    {
        perror("open err");
        return -1;
    }
    return 0;
}

closedir关闭文件目录

closedir 关闭目录
int closedir(DIR *dirp);
   功能:关闭目录
   参数:dirp:目录流指针
   返回值:成功0,失败-1,更新errno

readdir读取目录内容

#include <dirent.h>

readdir 读目录文件
struct dirent *readdir(DIR *dirp);
    功能:读目录文件
    参数:dirp:目录流指针
      返回值:成功返回结构体指针,读到文件结尾返回NULL
         失败NULL,更新errno
   struct dirent {
     ino_t          d_ino;       /*文件的inode */
     off_t          d_off;       /* not an offset; see NOTES */
     unsigned short d_reclen;    /* length of this record */
     unsigned char  d_type;      /* 文件类型,并不支持所有文件类型 */
     char           d_name[256]; /*文件名 */
 };
#include <stdio.h>
#include <sys/types.h>
#include <dirent.h>

int main(int argc, char const *argv[])
{
    struct dirent *dir;
    DIR *dirp = opendir("./");
    if (dirp == NULL)
    {
        perror("open err");
        return -1;
    }
    //读取文件夹中的内容
    while ((dir = readdir(dirp)) != NULL)
    {
        printf("filename = %s\n",dir->d_name);
    }
    closedir(dirp);
    return 0;
}

chdir修改当前所处的路径

chdir 修改当前所处路径
     #include <unistd.h>
       int chdir(const char *path);
     功能:改变当前所处的路径
     参数:path:修改的路径、
     返回值:成功0
          失败:-1,更新errno

stat获取文件属性

基本用法
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>

int stat(const char *pathname, struct stat *buf);
功能:获取文件属性
参数:
    pathname:文件
    buf:获取到属性存放的位置
返回值:成功0,失败-1,更新errno

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;        /* Device ID (if special file) */
   off_t     st_size;        /* 大小 */
   blksize_t st_blksize;     /* 文件系统IO块的大小 */
   blkcnt_t  st_blocks;      /* 512b分配的数量 */

   struct timespec st_atim;  /* Time of last access 上次访问时间*/
   struct timespec st_mtim;  /* Time of last modification 上次修改时间 */                                     
   struct timespec st_ctim;  /* Time of last status change上次状态更改时间 */
}

例子 :获取文件的inode号

#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>

int main(int argc, char const *argv[])
{
    //通过stat函数获取文件的信息
    struct stat sb;
    if (stat("./1.c",&sb) == -1)
    {
       perror("stat err");
       return -1;
    }
    printf("inode = %ld\n",st_ino);
    printf("dev = %ld\n",sb.st_dev);
    return 0;
}
获取文件类型
在man手册的第七页,打开man 7 inode
The following mask values are defined for the file type:
对于该文件类型,定义了以下掩码值:
st_mode 包含的是文件类型和权限:
    文件类型:
    S_IFMT     0170000   bit mask for the file type bit field   文件类型位字段的位掩码
    S_IFSOCK   0140000   socket-套接字
     S_IFLNK    0120000   symbolic link-连接文件
     S_IFREG    0100000   regular file-普通文件
     S_IFBLK    0060000   block device块设备
     S_IFDIR    0040000   directory目录
     S_IFCHR    0020000   character 字符设备
     S_IFIFO    0010000   FIFO管道

使用规则

#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>

int main(int argc, char const *argv[])
{
    //通过stat函数获取文件的信息
    struct stat sb;
    if (stat("./1.c",&sb) == -1)
    {
       perror("stat err");
       return -1;
    }
    printf("mode = %#o\n",sb.st_mode);

    return 0;
}
100666
001 000 000 110 110 110
&
001 111 000 000 000 000 
170000
 =
001 000 000 000 000 000
100000
stat(pathname, &sb);
if ((sb.st_mode & S_IFMT) == S_IFREG)
{
    printf(“普通文件\n”);
}

因为使用频繁,所以有了更简便的方法

         S_ISREG(m)  is it a regular file?

           S_ISDIR(m)  directory?

           S_ISCHR(m)  character device?

           S_ISBLK(m)  block device?

           S_ISFIFO(m) FIFO (named pipe)?

           S_ISLNK(m)  symbolic link?  (Not in POSIX.1-1996.)

           S_ISSOCK(m) socket?  (Not in POSIX.1-1996.)

用法

stat(pathname, &sb);
if (S_ISREG(sb.st_mode))
{
    printf(“普通文件”);
}
if (S_ISFIFO(sb.st_mode))
{
    printf("管道文件");
}
其他结构体成员
 #include <pwd.h>
1.getpwuid:通过用户id获取用户名
   struct passwd *getpwuid(uid_t uid);
   struct passwd {
        char   *pw_name;       /* username */
        char   *pw_passwd;     /* user password */
        uid_t   pw_uid;        /* user ID */
        gid_t   pw_gid;        /* group ID */
        char   *pw_gecos;      /* user information */
        char   *pw_dir;        /* home directory */
        char   *pw_shell;      /* shell program */
    };
getpwuid(sb.st_uid)->pw_name
 #include <grp.h>
  2.getgrgid:通过组id获取组名
  struct group *getgrgid(gid_t gid);
  struct group {
      char   *gr_name;       /* group name */
      char   *gr_passwd;     /* group password */
      gid_t   gr_gid;        /* group ID */
      char  **gr_mem;        /* group members */
  };  
3. ctime();将时间转转换为字符串表示格式
char *ctime(const time_t *timep);
如:转换为-"Wed Jun 30 21:49:08 1993\n"

例子

#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <pwd.h>
#include <grp.h>
#include <time.h>

int main(int argc, char const *argv[])
{
    // 通过stat函数获取文件的信息
    struct stat sb;
    if (stat("./1.c", &sb) == -1)
    {
        perror("stat err");
        return -1;
    }
    //printf("mode = %#o\n", sb.st_mode);
    if ((sb.st_mode & S_IFMT) == S_IFREG)
    {
        printf("-");
    }
    if (sb.st_mode & S_IRUSR)
    {
        putchar('r');
    }
    else
    {
        putchar('-');
    }
    if (sb.st_mode & S_IWUSR)
    {
        putchar('w');
    }
    else
    {
        putchar('-');
    }
    printf("%s ", getpwuid(sb.st_uid)->pw_name);
    printf("%s ", getgrgid(sb.st_gid)->gr_name);
    printf("%s ", ctime(&sb.st_mtime));
    return 0;
}
例子

模仿 ls -l

#include <stdio.h>
#include <sys/types.h>
#include <dirent.h>
#include <unistd.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdlib.h>
#include <pwd.h>
#include <grp.h>
#include <time.h>
int main(int argc, char const *argv[])
{
    struct stat *sb = (struct stat *)malloc(sizeof(struct stat));
    struct dirent *myls;
    DIR *dirp = opendir("./");
    if (dirp == NULL)
    {
        perror("open err");
        return -1;
    }
    while ((myls = readdir(dirp)) != NULL)
    {
        if (stat(myls->d_name, sb))
        {
            perror("stat err");
            return -1;
        }
        if (myls->d_name[0] == '.')
            continue;
        printf("%s", S_ISREG(sb->st_mode) ? "-" : S_ISDIR(sb->st_mode) ? "d"
                                              : S_ISCHR(sb->st_mode)   ? "c"
                                              : S_ISBLK(sb->st_mode)   ? "b"
                                              : S_ISFIFO(sb->st_mode)  ? "p"
                                              : S_ISLNK(sb->st_mode)   ? "L"
                                              : S_ISSOCK(sb->st_mode)  ? "s"
                                                                       : "error\n");
        sb->st_mode &S_IRUSR ? putchar('r') : putchar('-');
        sb->st_mode &S_IWUSR ? putchar('w') : putchar('-');
        sb->st_mode &S_IXUSR ? putchar('x') : putchar('-');
        sb->st_mode &S_IRGRP ? putchar('r') : putchar('-');
        sb->st_mode &S_IWGRP ? putchar('w') : putchar('-');
        sb->st_mode &S_IXGRP ? putchar('x') : putchar('-');
        sb->st_mode &S_IROTH ? putchar('r') : putchar('-');
        sb->st_mode &S_IWOTH ? putchar('w') : putchar('-');
        sb->st_mode &S_IXOTH ? putchar('x') : putchar('-');
        printf(" %ld %s %s ", sb->st_nlink, getpwuid(sb->st_uid)->pw_name,
               getgrgid(sb->st_gid)->gr_name);
        printf("%7ld ", sb->st_size);
        printf("%.*s ", 12, ctime(&sb->st_mtime)+4);
        printf("%s\n", myls->d_name);
    }
    free(sb);
    sb = NULL;
    closedir(dirp);
    return 0;
}

上一篇
下一篇