四、文件内核数据结构和原子操作

4.1 缓存 buff 说明

  一般设置缓存 buff  的大小是由一定的规律的,就是根据磁盘块的大小来定。

  Linux下输入命令: df -k  查看磁盘

  

  可以用命令查看下 /dev/sda1 磁盘的磁盘说明

1 sudo tune2fs -l /dev/sda1

  

  Block size 就是磁盘块的大小,这个磁盘块的大小为 4M ,那么就可以设置缓存 buff 大小为 4096,一次就可以将数据写入。

  设置的缓存大小最好与磁盘块的大小保持一致,有利于提升读写文件的效率。

4.2 操作文件中内核数据结构简要介绍

  • 一个打开的文件再内核中使用三种数据结构表示

    • 文件描述符

      • 文件描述符标志
      • 文件表项指针
    • 文件表项
      • 文件状态标志

        • 读、写、追加、同步和非阻塞等状态标志  
      • 当前文件偏移量
      • i 节点表项指针
      • 引用计数器  
    • i 节点   
      • 文件类型和对该文件的操作函数指针
      • 当前文件长度
      • 文件所有者
      • 文件所在的设备、文件访问权限
      • 指向文件数据在磁盘上所在位置的指针等   

  

4.3 原子操作

4.3.1 介绍

  主要是open 函数中的文件追加和文件创建

  • 文件追加

    • 打开文件时,使用 O_APPEND 标志,进程对到文件偏移量调整和数据追加成为原子操作
    • 内核每次对文件写之前,都将进程的当前偏移量设置为该文件的尾端。这样不再需要 lseek 来调整偏移量  
  • 文件创建
    • 对 open 函数的 O_CREAT 和 O_EXCL 的同时使用,而该文件存在,open 将失败,否则创建该文件,并且使得文件是否存在的判定和创建过程成为原子操作。  

  例子:两个进程对同一文件进行追加,没有使用 append 的时候

  file_append.c

 1 #include <sys/types.h>
 2 #include <sys/stat.h>
 3 #include <fcntl.h>
 4 #include <unistd.h>
 5 #include <string.h>
 6 #include <errno.h>
 7 #include <stdlib.h>
 8 #include <stdio.h>
 9 #include <fcntl.h>
10
11 int main(int argc, char *argv[])
12 {
13     if(argc < 3) {
14         fprintf(stderr, "usage: %s content destfile\n", argv[0]);
15         exit(1);
16     }
17
18     int fd;
19     int ret;
20     size_t size;
21
22     fd = open(argv[2], O_WRONLY);
23     if(fd < 0){
24         perror("open error");
25         exit(1);
26     }
27
28     //定位到文件尾部
29     ret = lseek(fd, 0L, SEEK_END);
30     if(ret == -1) {
31         perror("lseek error");
32         close(fd);
33         exit(1);
34     }
35
36     sleep(10); //睡眠 10s
37
38     //往文件中追加内容
39     size = strlen(argv[1]) * sizeof(char);
40     if(write(fd, argv[1], size) != size) {
41         perror("write error");
42         close(fd);
43         exit(1);
44     }
45
46     return 0;
47 }

  编译:gcc -o bin/file_append src/file_append.c

  创建一个  append.txt 文件,然后开启两个终端运行此程序

  第一个终端:

  第二各终端:

  第二个终端在第一个终端之后运行,运行完之后,查看 append.txt 的内容:

  

  现象上说明,第二个终端的写入将第一个终端的写入给覆盖掉了。

  第一个进程运行的时候,文件表项中的当前偏移量来源于 i 节点的文件长度(即调用 lseek 的时候),第二个进程运行的时候也是用 lseek 来获取偏移量,但是 i 节点中的文件长度没有增加,所以文件表项中的 当前偏移量 依然未变,因此第二个进程追加的内容覆盖掉了第一个进程中的内容。

  要想不覆盖,则要使用原子操作。将 open 和 注释掉 lseek 的代码做修改:

1     //fd = open(argv[2], O_WRONLY);
2     fd = open(argv[2], O_WRONLY | O_APPEND);

  删除 appent.txt 中的内容,然后再次在两个终端中运行两个程序:

  

  加了 O_APPEND 后,write 函数做了几件事情,此时整个 write 成为一个原子操作,只有当第一个进程的 write 执行完后,第二个进程的 write 才后执行:

  1. 从 i 节点中读取文件长度作为当前偏移量
  2. 往文件中写入数据
  3. 修改 i 节点中文件操作

原文地址:https://www.cnblogs.com/kele-dad/p/9033195.html

时间: 2024-11-25 18:59:12

四、文件内核数据结构和原子操作的相关文章

第3章 文件I/O(3)_内核数据结构、原子操作

3. 文件I/O的内核数据结构 (1) 内核数据结构表 数据结构 主要成员 文件描述符表 ①文件描述符标志 ②文件表项指针 文件表项 ①文件状态标志(读.写.追加.同步和非阻塞等状态标志) ②当前文件偏移量 ③i节点表项指针 ④引用计数器 i节点 ①文件类型和对该文件的操作函数指针 ②当前文件长度 ③文件所有者 ④文件所在设备.文件访问权限 ⑤指向文件数据在磁盘块上所在位置的指针等. (2)3张表的关系 4. 文件的原子操作 (1)文件追加 ①打开文件时使用O_APPEND标志,进程对文件偏移量

Linux内核设计基础(八)之内核数据结构

我个人比较喜欢学习数据结构,而Linux内核中实现的数据结构会是我们去学习.理解和应用数据结构的一个很好途径.这里介绍内核中广泛应用的四种数据结构:链表.队列.映射和二叉树. 链表: Linux内核讲求高效精简,所以有时需要我们动态去创建和分配内存,这时就要借助链表,我们根据实际情况分配内存后,只需修改链表的指针,仍能索引到刚分配的内存区.链表分单向链表.双向链表和循环链表. 单向链表 struct list_element { void *data; struct list_element *

linux内核数据结构学习总结(undone)

本文旨在整理内核和应用层分别涉及到的数据结构,从基础数据结构的角度来为内核研究作准备,会在今后的研究中不断补充 目录 1. 进程相关数据结构 1) struct task_struct 2. 内核中的队列/链表对象 3. 内核模块相关数据结构 2) struct module 1. 进程相关数据结构 0x1: task_struct 我们知道,在windows中使用PCB(进程控制块)来对进程的运行状态进行描述,对应的,在linux中使用task_struct结构体存储相关的进程信息,task_

从hook开始聊聊那些windows内核数据结构

总览: IAT HOOK Object Hook Ssdt Hook 源码 内核知识及源码 内核知识级源码 一.IAT HOOK:因为上一篇博客对已经对IAT Hook基本流程及作用进行了介绍,希望能先学懂PE再来看IATHook.下面贴上Iathook的源码,源码中有详细的注释,还记着为什么不能结束360的进程吗?参考思路如下图(因为写代码的时候解决方案写到了源码中,不粘贴复制过来了): 以下代码是DLL注入+iathook,通过测试procexp中的kill功能并没有使用OpenProces

Hadoop基于文件的数据结构及实例

基于文件的数据结构 两种文件格式: 1.SequenceFile 2.MapFile SequenceFile 1.SequenceFile文件是Hadoop用来存储二进制形式的<key,value>对而设计的一种平面文件(Flat File). 2.能够把SequenceFile当做一个容器,把全部文件打包到SequenceFile类中能够高效的对小文件进行存储和处理. 3.SequenceFile文件并不依照其存储的key进行排序存储.SequenceFile的内部类Writer**提供了

linux VFS 内核数据结构

<strong>简单归纳:fd只是一个整数,在open时产生.起到一个索引的作用,进程通过PCB中的文件描述符表找到该fd所指向的文件指针filp.</strong> 文件描述符的操作(如: open)返回的是一个文件描述符,内核会在每个进程空间中维护一个文件描述符表, 所有打开的文件都将通过此表中的文件描述符来引用;而流(如: fopen)返回的是一个FILE结构指针, FILE结构是包含有文件描述符的,FILE结构函数可以看作是对fd直接操作的系统调用的封装, 它的优点是带有I

《Linux内核设计与实现》读书笔记(六)- 内核数据结构

内核数据结构贯穿于整个内核代码中,这里介绍4个基本的内核数据结构. 利用这4个基本的数据结构,可以在编写内核代码时节约大量时间. 主要内容: 链表 队列 映射 红黑树 1. 链表 链表是linux内核中最简单,同时也是应用最广泛的数据结构. 内核中定义的是双向链表. 1.1 头文件简介 内核中关于链表定义的代码位于: include/linux/list.h list.h文件中对每个函数都有注释,这里就不详细说了. 其实刚开始只要先了解一个常用的链表操作(追加,删除,遍历)的实现方法, 其他方法

浏览器内核(四种内核)

浏览器内核有很多种,主流的有四种,这四种内核分别是Trident,Gecko,webkit,prestv. Trident----IE, Gecko----firefox, webkit-----safari + Goole chrome + 搜狗浏览器, prestv----opera(由于市场选择的问题,主要用于手机平台 opera mini) 浏览器内核主要是指什么呢? 浏览内核分为两部分:渲染引擎和js引擎,通过名字我们就知道:渲染引擎就是以何种方式显示网页,js引擎就是以何种方式执行j

Go语言移植Linux内核数据结构hlist

hlist(哈希链表)可以通过相应的Hash算法,迅速找到相关的链表Head及节点. 在有些应用场景,比Go标准库提供的list(一种双向链表)更合适. 依照list.h中的源码,我实现了一个Go语言版本的hlist例子. 首先说下hlist的构成:             在hlist(哈希链表)中, 头结点使用struct hlist_head来表示,hlist_head仅一个first指针. 普通节点使用struct hlist_node来表示. 源码中有几个特别的地方: 1. 在stru