Linux —— 基础I/O

一,背景介绍

狭义的文件存放在磁盘上,广义上在Linux下一切皆文件;磁盘上的文件一般为永久存储的外设,本质上对文件的操作,即为对外设的输入和输出(简称I/O);空文件并不是不占磁盘文件,只是没有内容,文件=属性(元数据)+内容;对文件的内容操作,如fread、fwrite、fgets、fgetc,fputc,fputs等;对文件的属性操作,如fseek、ftell、rewind等;

从系统角度,对文件的操作,其实就是进程对文件的操作;是通过系统调用接口,来实现操作的;

当前路径,每个进程都有一个内置属性cwd(即当前工作目录);

bash 复制代码
[wz@192 ~]$ ll /proc/29286
total 0
dr-xr-xr-x. 2 wz wz 0 8月   7 19:11 attr
-rw-r--r--. 1 wz wz 0 8月   7 19:11 autogroup
-r--------. 1 wz wz 0 8月   7 19:11 auxv
-r--r--r--. 1 wz wz 0 8月   7 19:11 cgroup
--w-------. 1 wz wz 0 8月   7 19:11 clear_refs
-r--r--r--. 1 wz wz 0 8月   7 19:10 cmdline
-rw-r--r--. 1 wz wz 0 8月   7 19:11 comm
-rw-r--r--. 1 wz wz 0 8月   7 19:11 coredump_filter
-r--r--r--. 1 wz wz 0 8月   7 19:11 cpuset
lrwxrwxrwx. 1 wz wz 0 8月   7 19:11 cwd -> /home/wz/Desktop
-r--------. 1 wz wz 0 8月   7 19:11 environ
lrwxrwxrwx. 1 wz wz 0 8月   7 19:11 exe -> /home/wz/Desktop/target
dr-x------. 2 wz wz 0 8月   7 19:10 fd
...

在任何C程序,都会默认打开三个文件(硬件):

  • 标准输入,stdin,键盘文件;
  • 标准输出,stdout,显示器文件;
  • 标准错误,stderr,显示器文件;

注,所有外设硬件,本质上即对应read/write的核心操作;可通过C接口,直接对stdin/stdout/stderr进行读写操作;默认打开这三个文件,其他函数使用需求,如scanf、printf、perror等,另外fprintf、fscanf;其他语言也是如此,C++中是cin、cout、cerr;

二,系统接口函数

open、close、read、write;

open 打开或创建文件

close 关闭文件

read 读取文件

write 写入文件

cpp 复制代码
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>

int main()
{
    int fd = open("log.txt", O_WRONLY|O_CREAT, 0644);
    if(fd<0)
    {
        perror("open");
        return -1;
    }
    const char* msg = "hello world\n";
    write(fd, msg, strlen(msg));
    close(fd);
    return 0; 
}
cpp 复制代码
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>

int main()
{
    int fd = open("log.txt", O_RDONLY);
    if(fd<0)
    {
        perror("open");
        return -1;
    }
    char buffer[1024];
    ssize_t s = read(fd, buffer, sizeof(buffer)-1);
    if(s>0)
    {
        buffer[s] = '\0';
        printf("%s\n", buffer);
    }
    close(fd);
    return 0; 
}
cpp 复制代码
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>

int main()
{
    int fd = open("log.txt", O_WRONLY|O_APPEND);
    if(fd<0)
    {
        perror("open");
        return -1;
    }
    const char* msg = "hello world append\n";
    write(fd, msg, strlen(msg));
    close(fd);
    return 0; 
}

注:

  • O_WRONLY|O_CREAT 对应C语言 w ;
  • O_RDONLY 对应C语言 r ;
  • O_WRONLY|O_APPEND 对应C语言 a ;
    标签flags,O_WRONLY、O_CREAT、O_RDONLY、O_APPEND都是宏;
cpp 复制代码
/usr/include/asm-generic/fcntl.h

#define O_ACCMODE 00000003
#define O_RDONLY  00000000
#define O_WRONLY  00000001
#define O_RDWR    00000002
#ifndef O_CREAT
#define O_CREAT   00000100  /* not fcntl */
#endif
#ifndef O_EXCL
#define O_EXCL    00000200  /* not fcntl */
#endif
#ifndef O_NOCTTY
#define O_NOCTTY  00000400  /* not fcntl */
#endif
#ifndef O_TRUNC
#define O_TRUNC   00001000  /* not fcntl */
#endif
#ifndef O_APPEND                                                                 
#define O_APPEND  00002000
#endif

编程语言使用自己的接口,封装系统接口,是因为需兼容自身语法特征,且系统调用使用成本较高,不具备可移植性;

三,文件描述符

Linux进程默认会有三个已打开的文件描述符,即0(stdin键盘)、1(stdout显示器)、2(stderr显示器);系统接口函数的返回值即为文件描述符,是从0开始的小整数,实际上是数组的下标;新文件的文件描述符使用当前没被使用的最小下标;

cpp 复制代码
#include <stdio.h>      
#include <sys/types.h>      
#include <sys/stat.h>      
#include <fcntl.h>      
#include <unistd.h>      
#include <string.h>   

int main()      
{      
    int fd1 = open("log1.txt", O_WRONLY|O_CREAT, 0644);      
    int fd2 = open("log2.txt", O_WRONLY|O_CREAT, 0644);      
    int fd3 = open("log3.txt", O_WRONLY|O_CREAT, 0644);      
    int fd4 = open("log4.txt", O_WRONLY|O_CREAT, 0644);      
      
    printf("fd1=%d\n",fd1);  
    close(fd1);    
    printf("fd2=%d\n",fd2);                                                          
    printf("fd3=%d\n",fd3);      
    printf("fd4=%d\n",fd4);      
    return 0;       
}   
bash 复制代码
//新建的文件会从3开始
[wz@192 Desktop]$ ./target 
fd1=3
fd2=4
fd3=5
fd4=6

结构体指针files

  • 进程task_struct包含一个结构体指针files;

结构体指针files指向的表files_struct

  • 该表包含一个结构体指针数组;
  • 该数组中元素指向打开的文件;

文件结构体

四,输出重定向

cpp 复制代码
//关闭默认打开的1指向的文件,即stdout
//此时新创建的文件,fd将会是1
//本来printf应打印到显示器,此时应该写入到log.txt
int main()    
{    
    close(1);    
    int fd = open("log.txt", O_WRONLY|O_CREAT, 0644);    
    printf("fd=%d\n",fd); 
    close(fd);                                                                                     
    return 0;    
}   
bash 复制代码
//此时不仅没有打印到屏幕,也没有写入到log.txt
[wz@192 Desktop]$ ./target 
[wz@192 Desktop]$ cat log.txt 
[wz@192 Desktop]$ 

FILE

  • FILE结构体内部包括,文件对应的文件描述符下标fd 、及应用层C语言提供的缓冲区数据
  • 向普通文件写入是全缓冲,向屏幕文件写入是行缓冲;
cpp 复制代码
int main()    
{    
    close(1);    
    int fd = open("log.txt", O_WRONLY|O_CREAT, 0644);    
    printf("fd=%d\n",fd); 
    fflush(stdout);
    close(fd);                                                                                     
    return 0;    
}   
bash 复制代码
//刷新到了log.txt文件
[wz@192 Desktop]$ ./target 
[wz@192 Desktop]$ cat log.txt 
log.txt fd=1
cpp 复制代码
//语言层次上
int main()    
{    
    printf("stdin fd=%d\n",stdin->_fileno);    
    printf("stdout fd=%d\n",stdout->_fileno);    
    printf("stderr fd=%d\n",stderr->_fileno);                                        
    FILE* fp = fopen("log.txt", "r");    
    printf("log.txt fd=%d\n",fp->_fileno);    
    pclose(fp);    
    return 0;    
} 
bash 复制代码
[wz@192 Desktop]$ ./target 
stdin fd=0
stdout fd=1
stderr fd=2
log.txt fd=3
相关推荐
眠修2 分钟前
Kuberrnetes 服务发布
linux·运维·服务器
即将头秃的程序媛3 小时前
centos 7.9安装tomcat,并实现开机自启
linux·运维·centos
fangeqin3 小时前
ubuntu源码安装python3.13遇到Could not build the ssl module!解决方法
linux·python·ubuntu·openssl
爱奥尼欧4 小时前
【Linux 系统】基础IO——Linux中对文件的理解
linux·服务器·microsoft
超喜欢下雨天5 小时前
服务器安装 ros2时遇到底层库依赖冲突的问题
linux·运维·服务器·ros2
tan77º6 小时前
【Linux网络编程】网络基础
linux·服务器·网络
笑衬人心。6 小时前
Ubuntu 22.04 + MySQL 8 无密码登录问题与 root 密码重置指南
linux·mysql·ubuntu
chanalbert8 小时前
CentOS系统新手指导手册
linux·运维·centos
星宸追风8 小时前
Ubuntu更换Home目录所在硬盘的过程
linux·运维·ubuntu
热爱生活的猴子9 小时前
Poetry 在 Linux 和 Windows 系统中的安装步骤
linux·运维·windows