Linux C编程之七(2) 系统IO函数

一、整体大纲

二、 系统IO函数
1. 一些概念
    文件描述符
     PCB
     C库函的IO缓冲区

     1) 文件描述符
            int 类型
            一个进程最多可打开多少文件
     2) pcb
           进程控制块
           在其中有一个文件描述符表 -- 数组[1024]

C库IO函数工作流程:

pcb和文件描述符:

 

2. 虚拟地址空间

    虚拟地址空间就是程序启动起来之后从硬盘上会有一块虚拟内存分配出来。

cpu 为什么要使用虚拟地址空间与物理地址空间映射?解决了什么样的问题?

    1)方便编译器和操作系统安排程序的地址分布。

         程序可以使用一系列相邻的虚拟地址来访问物理内存中不相邻的大内存缓冲区。通过虚拟地址空间与物理地址空间映射解决不连续的缓冲区的问题。

    2)方便进程之间隔离

        不同进程使用的虚拟地址彼此隔离。一个进程中的代码无法更改正在由另一进程使用的物理内存。 

    3)方便OS使用你那可怜的内存。

        程序可以使用一系列虚拟地址来访问大于可用物理内存的内存缓冲区。当物理内存的供应量变小时,
        内存管理器会将物理内存页(通常大小为 4 KB)保存到磁盘文件。数据或代码页会根据需要在物理内存与磁盘之间移动。

    虚拟地址空间的布局如下:

    0-3G是用户空间        3-4G是内核空间

       用户区                        内核区
       代码段
       已经初始化的全局变量
       未被初始化的全局变量
       堆 -- 从下往上
       共享库
       栈 - 从上往下
       环境变量
       内核区 

 

3. C库函数与系统函数的关系 

    FD:文件描述符 FP_POS:文件指针 BUFFER:缓冲区
    write对0-3G的用户空间进行操作 sys_write()对3-4G的内核空间进行操作

4. IO函数介绍

1)open

  • 查看 man 2 open

  • 头文件:

    #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);

  • 参数说明 

           pathname 文件名
           flags
                 必选项:
                         O_RDONLY 只读
                         O_WRONLY 只写
                         O_RDWR 读写
                 可选项:
                         O_APPEND 追加
                         O_CREAT 创建文件
                         O_EXCL和O_CREATE一起使用,如果文件存在则报错

O_NONBLOCK 非阻塞
            Mode 权限位,最终(mode&~umask)

  • 返回值:

                 成功:返回最小的可用文件描述符
                 失败:返回-1,并且设置errno

  • open函数中的errno:

1 1 #include<stdio.h> 2 2 #include<fcntl.h> 3 3 #include<sys/types.h> 4 4 #include<sys/stat.h> 5 5 #include<unistd.h> 6 6 7 7 int main(int argc, char *argv[]) 8 8 { 9 9 if (argc != 2) 1010 { 1111 printf("./a.out filename\n") 1212 return -1 1313 } 1414 int fd = open(argv[1], O_CREAT|O_TRUNC|O_WRONLY, 0666); 1515 close(fd); 1616 1717 return 0; 1818 }

使用open实现一个touch功能

1 1 #include<stdio.h> 2 2 #include<unistd.h> 3 3 #include<sys/types.h> 4 4 #include<sys/stat.h> 5 5 #include<fcntl.h> 6 6 #include<strings.h> 7 7 8 8 int main(int argc, char *argv[]) 9 9 { 1010 int num = 3; 1111 char filename[128] = {0}; 1212 while(1) 1313 { 1414 sprintf(filename, "temp_%04d", num++); 1515 if (open(filename, O_RDONLY|O_CREAT, 0666) < 0) 1616 { 1717 perror("open err:"); 1818 break; 1919 } 2020 } 2121 printf("num == %d\n", num); 2222 2323 return 0; 2424 }

一个进程打开的最大文件数(1024)

2)close

  • 作用:关闭文件描述符

  • 头文件:

    #include <unistd.h>

  • 函数原型:

    int close(int fd);

  • 参数说明:

          fd文件描述符

  • 返回值:

          成功:返回0
          失败:返回-1,并且设置errno

3)read读

  • 头文件

    #include <unistd.h>

  • 函数原型

    ssize_t read(int fd, void *buf, size_t count);

  • 参数说明

           fd 文件描述符

           buf缓冲区

           count缓冲区大小

  • 返回值

           失败:返回-1,设置errno
           成功:返回读到的字节数
                      0代表读到文件末尾
                      非阻塞的情况下read返回-1,但是此时需要判断error的值。

4)write写

  • 头文件

    #include <unistd.h>

  • 函数原型

    ssize_t write(int fd, const void *buf, size_t count);

  • 参数说明:

          fd文件描述符
          buf缓冲区
          count缓冲区大小

  • 返回值

          失败:返回-1,设置errno
          成功:返回写入的字节数
          0代表未写入

1 1 #include<stdio.h> 2 2 #include<fcntl.h> 3 3 #include<sys/types.h> 4 4 #include<sys/stat.h> 5 5 #include<unistd.h> 6 6 7 7 int main(int argc, char *argv[]) 8 8 { 9 9 if (argc != 2) 1010 { 1111 printf("./a.out filename\n") 1212 return -1 1313 } 1414 int fd = open(argv[1], O_RDONLY); 1515 char buf[256] = {0}; 1616 int ret = 0; 1717 while ((ret = read(fd, buf, ziseof(buf))) != 0) 1818 { 1919 if (ret == -1) 2020 { 2121 perror("read err:"); 2222 return -1; 2323 } 2424 else 2525 { 2626 write(STDOUT_FILENO, buf, ret); 2727 } 2828 } 2929 3030 close(fd); 3131 3232 return 0; 3333 }

实现一个cat功能

需求:给一个文件中写入内容,写完之后打开该文件再读取写入的内容?

1 1 #include<stdio.h> 2 2 #include<fcntl.h> 3 3 #include<sys/types.h> 4 4 #include<sys/stat.h> 5 5 #include<unistd.h> 6 6 7 7 int main(int argc, char *argv[]) 8 8 { 9 9 if (argc != 2) 1010 { 1111 printf("./a.out filename\n"); 1212 return -1; 1313 } 1414 int fd = open(argv[1], O_RDWR|O_CREAT, 0666); 1515 1616 char data[12] = "hello world"; 1717 write(fd, data, sizeof(data)); 1818 1919 char buf[256] = {0}; 2020 int ret = 0; 2121 while ((ret = read(fd, buf, sizeof(buf))) != 0) 2222 { 2323 if (ret == -1) 2424 { 2525 perror("read err:"); 2626 return -1; 2727 } 2828 else 2929 { 3030 write(STDOUT_FILENO, buf, ret); //STDIN_FILENO, STDERR_FILENO 3131 } 3232 } 3333 3434 close(fd); 3535 3636 return 0; 3737 }

bug版本

结果:内容写入到文件中,但是并未按预期输出到屏幕上。
原因:是由于write完成之后,fd到了文件末尾,因此read时到了文件末尾,无法读取文件数据
解决方法:写完之后将文件指针设置到文件开头,使用请看下文要介绍的lseek函数。

5)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
           成功:返回当前位置到文件开头的长度

  • lseek作用

          移动文件读写位置
          计算文件大小
          拓展文件

 示例:

a. 移动文件读写位置

1 1 #include<stdio.h> 2 2 #include<fcntl.h> 3 3 #include<sys/types.h> 4 4 #include<sys/stat.h> 5 5 #include<unistd.h> 6 6 7 7 int main(int argc, char *argv[]) 8 8 { 9 9 if (argc != 2) 1010 { 1111 printf("./a.out filename\n"); 1212 return -1; 1313 } 1414 int fd = open(argv[1], O_RDWR|O_CREAT, 0666); 1515 1616 char data[12] = "hello world"; 1717 write(fd, data, sizeof(data)); 1818 //文件读写位置此时在末尾 1919 //需要移动读写位置 2020 lseek(fd, 0, SEEK_SET); //将fd移动到文件头 2121 2222 char buf[256] = {0}; 2323 int ret = 0; 2424 while ((ret = read(fd, buf, sizeof(buf))) != 0) 2525 { 2626 if (ret == -1) 2727 { 2828 perror("read err:"); 2929 return -1; 3030 } 3131 else 3232 { 3333 write(STDOUT_FILENO, buf, ret); //STDIN_FILENO, STDERR_FILENO 3434 } 3535 } 3636 3737 close(fd); 3838 3939 return 0; 4040 }

修改上例的bug(写入文件内容并读取文件内容打印到屏幕)

b. 计算文件大小

1 1 #include<stdio.h> 2 2 #include<fcntl.h> 3 3 #include<sys/types.h> 4 4 #include<sys/stat.h> 5 5 #include<unistd.h> 6 6 7 7 int main(int argc, char *argv[]) 8 8 { 9 9 if (argc != 2) 1010 { 1111 printf("./a.out filename\n"); 1212 return -1; 1313 } 1414 int fd = open(argv[1], O_RDONLY); 1515 1616 int ret = lseek(fd, 0, SEEK_END); //将fd移动到文件头 1717 printf("file size is %d\n", ret); //注意实际读到的文件大小为ret-1 1818 1919 close(fd); 2020 2121 return 0; 2222 }

计算文件大小(输出文件字节数)

c. 拓展文件

1 1 #include<stdio.h> 2 2 #include<fcntl.h> 3 3 #include<sys/types.h> 4 4 #include<sys/stat.h> 5 5 #include<unistd.h> 6 6 7 7 int main(int argc, char *argv[]) 8 8 { 9 9 if (argc != 2) 1010 { 1111 printf("./a.out filename\n"); 1212 return -1; 1313 } 1414 int fd = open(argv[1], O_WRONLY|O_CREAT, 0666); 1515 //拓展文件 1616 int ret = lseek(fd, 1024, SEEK_END); //将fd移动到文件头 1717 //需要至少写一次,否则不能保存 1818 write(fd, "a", 1); 1919 printf("file size is %d\n", ret); 2020 2121 close(fd); 2222 2323 return 0; 2424 }

拓展文件

阻塞的概念:
       read函数在读设备或者读管道,或者读网络的时候。
       输入输出设备对应的/dev/tty。

6)fcntl

  • 头文件

    #include <unistd.h> #include <fcntl.h>

  • 函数原型

    int fcntl(int fd, int cmd, ... /* arg */ );

  • 参数说明:

           fd文件描述符
           cmd 命令

  • 返回值

           不同的cmd返回值不同

1 1 #include<stdio.h> 2 2 #include<fcntl.h> 3 3 #include<sys/types.h> 4 4 #include<sys/stat.h> 5 5 #include<unistd.h> 6 6 7 7 int main(int argc, char *argv[]) 8 8 { 9 9 //O_NONBLOCK设置为非阻塞 1010 int fd = open("/dev/tty", O_RDWR); 1111 //fcntl()函数,设置非阻塞 1212 int flags = fcntl(fd, F_GETFL); 1313 flags |= O_NONBLOCK; 1414 fcntl(fd, F_SETFL, flags); 1515 1616 char buf[256] = {0}; 1717 int ret = 0; 1818 while(1) 1919 { 2020 //如果没有设置O_NONBLOCK 2121 ret = read(fd, buf, sizeof(buf)); 2222 if (ret < 0) 2323 { 2424 perror("read err:"); 2525 printf("ret is %d\n", ret); 2626 } 2727 2828 if (ret) 2929 { 3030 printf("buf is %s\n", buf); 3131 } 3232 printf("haha\n"); 3333 sleep(1); 3434 } 3535 close(fd); 3636 3737 return 0; 3838 }

使用fcntl函数实现读非阻塞

点赞
收藏

评论区

加载中...

相关推荐

MySQL:[Err] 1292 - Incorrect datetime value: ‘0000-00-00 00:00:00‘ for column ‘CREATE_TIME‘ at row 1

文章目录问题用navicat导入数据时,报错:原因这是因为当前的MySQL不支持datetime为0的情况。解决修改sql\mode:sql\mode:SQLMode定义了MySQL应支持的SQL语法、数据校验等,这样可以更容易地在不同的环境中使用MySQL。全局s

Oracle 分组与拼接字符串同时使用

SELECTT.,ROWNUMIDFROM(SELECTT.EMPLID,T.NAME,T.BU,T.REALDEPART,T.FORMATDATE,SUM(T.S0)S0,MAX(UPDATETIME)CREATETIME,LISTAGG(TOCHAR(

MySQL部分从库上面因为大量的临时表tmp_table造成慢查询

背景描述Time:20190124T00:08:14.70572408:00User@Host:@Id:Schema:sentrymetaLast_errno:0Killed:0Query_time:0.315758Lock_

皕杰报表之UUID

​在我们用皕杰报表工具设计填报报表时,如何在新增行里自动增加id呢?能新增整数排序id吗?目前可以在新增行里自动增加id,但只能用uuid函数增加UUID编码,不能新增整数排序id。uuid函数说明:获取一个UUID,可以在填报表中用来创建数据ID语法:uuid()或uuid(sep)参数说明:sep布尔值,生成的uuid中是否包含分隔符'',缺省为

手写Java HashMap源码

HashMap的使用教程HashMap的使用教程HashMap的使用教程HashMap的使用教程HashMap的使用教程22

一篇文章带你了解JavaScript日期

日期对象允许您使用日期(年、月、日、小时、分钟、秒和毫秒)。一、JavaScript的日期格式一个JavaScript日期可以写为一个字符串:ThuFeb02201909:59:51GMT0800(中国标准时间)或者是一个数字:1486000791164写数字的日期,指定的毫秒数自1970年1月1日00:00:00到现在。1\.显示日期使用

Linux C编程之七(2) 系统IO函数 - HelloWorld