Unix系统编程(1) - 文件IO

Unix系统编程文件IO系列博客,源于《Linux C一站式编程》的阅读笔记和《APUE2》的阅读笔记。

Unix系统可用的文件IO函数包括打开文件、读文件、写文件等等。

1、文件描述符

对于内核来说,所有打开文件都通过文件描述符引用。文件描述符是一个非负的整数,当打开一个现有的文件或者创建一个文件的时候,内核向进程返回一个文件描述符。当读写一个文件时,使用open或create返回文件描述符标识该文件,将其作为参数传给read或write。

POSIX标准程序中0,1,2分别表示标准输入,标准输出,标准错误相关联:

  • STDIN_FILENO 0
  • STDOUT_FILENO 1
  • STDERR_FILENO 2

新打开文件返回文件描述符表中未使用的最小文件描述符。文件描述符的变化范围为:0 ~ OPEN_MAX - 1。该值存在于/proc/sys/fs/file_max文件下,使用cat命令查看该值可以得到当前系统允许打开的最大文件个数。

picture1

使用ulimit命令查看当前默认设置的最大打开文件个数:
picture2

可以看到当前默认设置的最大打开文件个数是1024个,这个数值可以通过ulimit命令进行修改。下图中将该值修改成4096。

picture3

2、比较系统函数和C标准函数

我们知道,C 标准提供的函数我们可以再不用的平台(不管是windows, Linux, Unix, Mac, 甚至嵌入式芯片等)使用。那么也就是说C 标准函数并不依赖于硬件。不管是在Windows下还是Linux下,系统都会给我们提供一套所谓的API函数,这些函数也就是直接和操作系统交互的函数。当然,这些函数是依赖于硬件平台的。

那么,上述的这两者之间是什么关系? 其实,C 标准函数是建立在系统函数之上的一套函数。在Unix系统中,系统函数负责和系统内核进行交互,C标准函数则是通过调用系统函数实现对内核的交互的。他们之间的关系可以通过下图看到:

picture5


C 标准库函数带有一个buffer, 在写文件过程中,会先把内容写到buffer中,当buffer中的内容写满了,或者主动要求刷新的时候,才会将内容写到磁盘中。每一个FILE文件流都有一个缓冲区buffer,默认大小8192Byte。

系统函数则会直接向磁盘文件中写内容。

1- 用Unbuffered I/O函数每次读写都要进内核 调一个系统调用比调一个用户空间的函数要慢很多, 所以在用户空间开辟I/O缓冲区还是必要的,用C标准I/O库函数就比较方便,省去了自己管理I/O缓冲区的麻烦。
2- 用C标准I/O库函数要时刻注意I/O缓冲区和实际文件有可能不一致,在必要时需调用 fflush(3)
3- 当网络设备接收到数据时应用程序也希望第一时间被通知到,所以网络编程通常直接调用Unbuffered I/O函数。

3、进程控制块(PCB, Processing Control Block)

进程控制块(PCB)是系统为了管理进程设置的一个专门的数据结构。系统用它来记录进程的外部特征,描述进程的运动变化过程。同时,系统可以利用PCB来控制和管理进程,所以说,PCB(进程控制块)是系统感知进程存在的唯一标志。
每个进程都会有两个和进程相关的结构体:

  • task_struct 位于/usr/src/linux-headers/include/linux/sched.h下
  • files_struct

在linux 中每一个进程都由task_struct 数据结构来定义. task_struct就是我们通常所说的PCB。它是对进程控制的唯一手段也是最有效的手段.

4、文件打开open

使用man 2 open 命令查看open函数的定义及相关的描述:

 NAME
       open, creat - open and possibly create a file or device
SYNOPSIS
       #include <sys/types.h>
       #include <sys/stat.h>
       #include <fcntl.h>
       int open(const char *pathname, int flags);
       int open(const char *pathname, int flags, mode_t mode);
       int creat(const char *pathname, mode_t mode);


从man的结果上看,系统提供给我们了两个open函数,两者的不同在于参数的个数。

  • const char *pathname
    该参数表示的是要要打开文件的文件路径。和fopen一样,pathname既可以是相对路径也可以是绝对路径。
  • int flags
    。flags参数有一系列常数值可供选择,可以同时选择多个常数用按位或运算符连接起来,所以这些常数的宏定义都以O_开头,表示or。
    必选项:以下三个常数中必须指定一个,且仅允许指定一个。
  • O_RDONLY 只读打开
  • O_WRONLY 只写打开
  • O_RDWR 可读可写打开

可选项 有很多,需要使用的时候可以使用man去查看,下面列举部分可选项:

  • O_APPEND 表示追加。如果文件已有内容,这次打开文件所写的数据附加到文件的末尾
    而不覆盖原来的内容。
  • O_CREAT 若此文件不存在则创建它。使用此选项时需要提供第三个参数mode,表示该
    文件的访问权限。
  • O_EXCL 如果同时指定了O_CREAT,并且文件已存在,则出错返回。
  • O_TRUNC 如果文件已存在,并且以只写或可读可写方式打开,则将其长度截断(Truncate)
    为0字节。
  • O_NONBLOCK 对于设备文件,以O_NONBLOCK方式打开可以做非阻塞I/O(Nonblock I/
    O),非阻塞I/O在下一节详细讲解。

  • mode_t mode
    指定文件权限,可以用八进制数表示,比如0644表示-rw-r-r–,也可以用S_IRUSR、S_IWUSR等宏定义按位或起来表示,详见open(2)的Man Page。要注意的是,文件权限由open的mode参数和当前进程的umask掩码共同决定。补充说明一下Shell的umask命令。Shell进程的umask掩码可以用umask命令查看:

open() 和 C标准函数fopen()的区别

1、以可写的方式fopen一个文件时,如果文件不存在会自动创建,而open一个文件时必须明确指定O_CREAT才会创建文件,否则文件不存在就出错返回。

2、以w或w+方式fopen一个文件时,如果文件已存在就截断为0字节,而open一个文件时必须明确指定O_TRUNC才会截断文件,否则直接在原来的数据上改写。

5、文件关闭close

NAME
       close - close a file descriptor
SYNOPSIS
       #include <unistd.h>
       int close(int fd);


关闭函数比较简单,直接传入一个需要关闭的文件的文件描述符就可以了。

  • 注意
    一个进程终止时,内核对该进程所有尚未关闭的文件描述符调用close关闭,所以即使用户程序不调用close,在终止时内核也会自动关闭它打开的所有文件。但是对于一个长年累月运行的程序(比如网络服务器),打开的文件描述符一定要记得关闭,否则随着打开的文件越来越多,会占用大量文件描述符和系统资源。

6、文件读写read/write

系统给出的两个函数说明:

NAME
       read - read from a file descriptor
SYNOPSIS
       #include <unistd.h>
       ssize_t read(int fd, void *buf, size_t count);


NAME
       write - write to a file descriptor
SYNOPSIS
       #include <unistd.h>
       ssize_t write(int fd, const void *buf, size_t count);

  • size_t类型和ssize_t类型:
    • ssize_t:表示有符号数
    • size_t:表示无符号数

7、编程试验

7.1 open函数使用

测试使用open函数,打开一个名为open.txt的文件,文件不存在则创建。

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

#include <stdio.h>
#include <stdlib.h>

int main(int args, char* argv[])
{
        printf("Test Unix API open function\n");
        int rt = open("open.txt", O_CREAT, 0777);
        if(rt == -1)
        {
                printf("open file error!\n");
                exit(1);
        }
        return 0;
}

7.2 测试最大文件打开个数

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

#include <stdio.h>
#include <stdlib.h>


int main(int args, char* argv[])
{
        //printf("Test Unix API open function\n");
        int index = 0;
        char name[1024] = {0};
        int rt = 0;
        while(1)
        {
                sprintf(name, "file%04d", ++index);
                if(-1 == open(name, O_CREAT, 0777))
                {
                        printf("error!\n");
                        exit(1);
                }
        }
        return 0;
}

7.3 终端读写测试

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

#include <stdio.h>
#include <stdlib.h>

int main(int args,char* argv[])
{
        char buf[127] = {0};
        int cnt = 0;
        cnt = read(STDIN_FILENO, buf, 127);
        if(cnt < 0)
        {
                perror("Read STDIN_FILENO error!");
                exit(1);
        }
        write(STDOUT_FILENO, buf, cnt);
        return 0;
}

7.4 读入文件,拷贝写入另一文件

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

#define SIZE    8192

int main(int argc, char *argv[])
{
    char buf[SIZE];
    int fd_src, fd_dest, len;
    if (argc < 3) {
        printf("agrc error\n");
        exit(1);
    }
    fd_src = open(argv[1], O_RDONLY);
    fd_dest = open(argv[2], O_CREAT | O_WRONLY | O_TRUNC, 0644);
    while ((len = read(fd_src, buf, sizeof(buf))) > 0)
        write(fd_dest, buf, len);
    close(fd_src);
    close(fd_dest);
    return 0;
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

空空的司马

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值