4.lseek函数及共享文件

Posted Rston

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了4.lseek函数及共享文件相关的知识,希望对你有一定的参考价值。


4.1.lseek函数介绍
(1)当我们要对1个文件进行读写时,首先需打开该文件,则我们读写的所有文件都是动态文件;动态文件在内存中就是以文件流的形式存在的。
(2)文件流很长,里面有很多个字符,我们需要确定当前正在操作的是哪个位置;GUI模式下的软件用光标来标识当前正在操作文件流的哪个位置;在动态文件中则通过文件指针(即Vnode结构体中的1个元素)来标识当前正在操作文件流哪个位置;文件指针不能被直接访问,linux系统使用lseek函数来访问该文件指针。
(3)当我们打开1个空文件时,默认情况下文件指针指向文件流的起始位置,则这时候去读写该文件时就是从文件流的起始位置开始的;write和read函数本身自带移动文件指针的功能,所以当我读写了n个字节后,文件指针会自动向后移动n位;如果需要人为的随意更改文件指针,那就只能通过lseek函数了。
(4)read和write函数都是从当前文件指针处开始操作的,所以当我们用lseek显式的将文件指针移动后,那么再去read/write时就是从移动过后的位置开始的;则当我们操作空文件时先write写了12字节,然后read时是空的,但是此时我们打开文件后发现12字节确实写进来了。


4.2.计算文件长度和构建空洞文件
(1)linux中并没有1个函数可以直接返回1个文件的长度,但是我们做项目时经常会需要知道1个文件的长度,我们自己利用lseek来写1个函数得到文件长度即可。
(2)空洞文件即该文件中有1段是空的;普通文件中间是不能有空的,因为我们write时文件指针是依次从前到后去移动的,我们不可能绕过前面直接到后面;我们打开某个文件后,用lseek往后跳过1段,再write写入1段,则会构成1个空洞文件。
(3)空洞文件方法对多线程共同操作文件是及其有用的,有时候我们创建1个很大的文件,如果从头开始依次构建时间很长,有1种思路就是将文件分为多段,然后多线程来操作,每个线程负责其中1段的写入。


4.3.重复打开同一文件读取
(1)1个进程中两次打开同一个文件,然后分别读取,1种情况是fd1和fd2分别读,1种情况是接续读;经过实验验证,证明了结果是fd1和fd2分别读。
(2)分别读说明我们使用open两次打开同一个文件时,fd1和fd2所对应的文件指针(fd->文件表指针->文件表->文件指针)是不同的2个独立的指针;文件指针是包含在文件表中的,所以可以看出linux系统的进程中不同fd对应的是不同的独立的文件表。


4.4.重复打开同一文件写入
(1)1个进程中两次打开同一个文件,然后分别写入,1种情况是fd1和fd2分别写,1种情况是接续写;经过实验验证,证明了结果是fd1和fd2分别写。
(2)正常情况下我们有时候需要分别写,有时候又需要接续写,所以这两种本身是没有好坏之分的,关键看用户需求;有时候我们希望接续写而不是分别写,办法就是在open时加O_APPEND标志即可。


4.5.O_APPEND实现原理及原子操作性说明
(1)分别写的内部原理就是2个fd拥有不同的文件指针,并且彼此只考虑自己的位移;但是O_APPEND标志可以让write和read函数内部多做1件事情,即移动自己的文件指针的同时也移动别人的文件指针(即fd1和fd2还是各自拥有1个独立的文件指针,但是这两个文件指针关联起来了,1个动了会通知另1个跟着动)。
(2)原子操作的含义即整个操作一旦开始是不会被打断的,必须直到操作结束其它代码才能得以调度运行,这就叫原子操作;每种操作系统中都有一些机制来实现原子操作,以保证那些需要原子操作的任务可以运行;O_APPEND对文件指针的影响,对文件的读写是原子的。


4.6.文件共享及实现方式
(1)文件共享即同一个文件(即同一个inode,同一个pathname)被多个独立的读写体(即多个文件描述符)同时(一个已打开尚未关闭的同时另一个去操作)操作;文件共享的意义有很多,譬如我们可以通过文件共享来实现多线程同时操作同1个大文件,以减少文件读写时间,提升效率。
(2)文件共享的核心即制造多个文件描述符指向同一个文件;常见的有3种文件共享的情况,第1种是同一个进程中多次使用open打开同一个文件;第2种是在不同进程中去分别使用open打开同一个文件(两个fd在不同的进程中,则两个fd的数字可以相同也可以不同);第3种情况是linux系统提供了dup和dup2两个API来让进程复制文件描述符;分析文件共享时的核心关注点在于确认是分别写/读还是接续写/读。
(3)当两个文件指针分别独立且互不关联->分别写/读;当两个文件指针分别独立且相互关联/两个文件指针相同->接续写/读(见图1)。


4.7.再论文件描述符
(1)文件描述符的本质是1个数字,该数字本质上是进程表中文件描述符表的1个表项,进程通过文件描述符作为index去索引查表得到文件表指针,再间接访问得到该文件对应的文件表。
(2)文件描述符是open系统调用内部由操作系统自动分配的,操作系统规定fd从0开始依次增加;fd也是有最大限制的,在linux的早期版本中(0.11)fd最大是20,所以当时1个进程最多允许打开20个文件;linux中文件描述符表是个指针数组(不是链表),其中fd是index,文件表指针是value。
(3)当我们去open时,内核会从文件描述符表中挑选1个最小的未被使用的数字给我们返回;即如果之前fd已经占满了0-9,那我们下次open得到的一定是10(但是如果上一个fd得到的是9,下一个不一定是10,这是因为可能前面更小的一个fd已经被close释放掉了)。
(4)fd中0、1、2已经默认被系统内核占用了,因此用户进程得到的最小的fd就是3了;当我们运行1个程序得到1个进程时,内部默认已打开3个文件,其对应的fd就是0、1、2;这3个文件分别叫stdin、stdout、stderr,即标准输入、标准输出、标准错误。
(5)标准输入一般对应的是键盘(0这个fd对应的是键盘的设备文件),标准输出一般是LCD显示器(1对应LCD的设备文件);printf函数其实就是默认输出到标准输出stdout上了,fpirntf函数可以指定输出到哪个文件描述符中。


这里写图片描述


4.lseek_example
/*
 * 公司:XXXX
 * 作者:Rston
 * 博客:http://blog.csdn.net/rston
 * GitHub:https://github.com/rston
 * 项目:lseek函数及共享文件
 * 功能:演示lseek函数的基本使用。
 */
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>
#include <stdlib.h>

int main(int argc, char **argv)
{
    int fd = -1;                                // fd即file descriptor,文件描述符     
    int ret = -1;                               // 用于接收read返回值,判断是否读文件成功
    char buf[100] = {0};                        // 构建缓冲区存放从文件读出的内容
    char bufwrite[20] = "linux is great";       // 构建缓冲区存放要写入文件的内容

#if 0   
    // 打开test.txt文件,若该文件不存在则创建(权限666),若该文件存在则报错
    fd = open("test.txt", O_RDWR | O_CREAT | O_EXCL, 0666); 
#else
    // 打开test.txt文件,若文件不存在则报错
    fd = open("test.txt", O_RDONLY);
#endif  
    if (-1 == fd)                               // 判断文件打开是否成功,也可这样写if (fd < 0)
    {
        //printf("open file error.\\n");
        perror("open file error");              // 通过perror捕捉errno错误信息并打印输出
        //return -1;                            // 使用return或exit退出进程或程序
        exit(-1);
    }
    else
    {
        printf("open file sucess. fd = %d.\\n", fd);
    }

#if 1   
    ret = lseek(fd, 3, SEEK_SET);               // 使用lseek函数从文件开始位置开始向后移动3个字符位置
    printf("lseek, ret = %d.\\n", ret);
#endif

#if 0   
    ret = write(fd, bufwrite, strlen(bufwrite));    // 写内容到文件中
    if (ret < 0)                                    // 判断内容是否成功写入文件
    {
        //printf("write file errot.\\n");
        perror("write file errot");
        //return -1;
        exit(-1);
    }
    else
    {
        printf("write %d bytes to file.\\n", ret);
        printf("the content of write is [%s].\\n", bufwrite);
    }
#endif

#if 1   
    ret = read(fd, buf, sizeof(buf));   // 读取文件内容
    if (ret < 0)                        // 判断读取文件是否成功
    {
        //printf("read file error.\\n");
        perror("read file error");
        //return -1;
        exit(-1);
    }
    else
    {
        printf("read %d bytes actual from file.\\n", ret);
        printf("the content of read is [%s].\\n", buf);
    }
#endif

    close(fd);                          // 关闭文件

    //return 0;
    exit(0);
}

4.lseek_callen
 /*
 * 公司:XXXX
 * 作者:Rston
 * 博客:http://blog.csdn.net/rston
 * GitHub:https://github.com/rston
 * 项目:lseek函数及共享文件
 * 功能:使用lseek函数测试某个文件的大小。
 */
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>
#include <stdlib.h>

/* 测试文件的大小,形参为路径名 */
int callen(const char *pathname)
{
    int fd = -1, ret = -1;

    fd = open(pathname, O_RDONLY);
    if (-1 == fd)
    {
        perror("open file error");
        exit(-1);
    }

    // lseek将文件指针移动到末尾,返回值即文件指针距离文件开头的偏移量,即文件的长度
    ret = lseek(fd, 0, SEEK_END);
    return ret;
}

int main(int argc, char **argv)
{
    if (argc != 2)
    {
        printf("usage: %s pathname.\\n", argv[0]);
        exit(-1);
    }

    printf("The size of %s is %d.\\n", argv[1], callen(argv[1]));

    exit(0);
}

4.lseek_empty
/*
 * 公司:XXXX
 * 作者:Rston
 * 博客:http://blog.csdn.net/rston
 * GitHub:https://github.com/rston
 * 项目:lseek函数及共享文件
 * 功能:使用lseek构建1个空洞文件。
 */
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>
#include <stdlib.h>

int main(int argc, char **argv)
{
    int fd = -1;                                // fd即file descriptor,文件描述符     
    int ret = -1;                               // 用于接收read返回值,判断是否读文件成功
    char buf[100] = {0};                        // 构建缓冲区存放从文件读出的内容
    char bufwrite[20] = "linux is great";       // 构建缓冲区存放要写入文件的内容

    // 打开test.txt文件,若该文件不存在则创建(权限666),若该文件存在则报错
    fd = open("test.txt", O_RDWR | O_CREAT | O_EXCL, 0666); 
    if (-1 == fd)                               // 判断文件打开是否成功,也可这样写if (fd < 0)
    {
        perror("open file error");              // 通过perror捕捉errno错误信息并打印输出
        exit(-1);
    }

#if 1   
    ret = lseek(fd, 10, SEEK_SET);              // 使用lseek函数从文件开始位置开始向后移动10个字符位置
    printf("lseek, ret = %d.\\n", ret);          // 即构建了1个空洞文件
#endif

#if 1   
    ret = write(fd, bufwrite, strlen(bufwrite));// 写内容到文件中
    if (ret < 0)                                // 判断内容是否成功写入文件
    {
        perror("write file errot");
        exit(-1);
    }
#endif

    close(fd);                                  // 关闭文件

    exit(0);
}

4.multi_read
/*
 * 公司:XXXX
 * 作者:Rston
 * 博客:http://blog.csdn.net/rston
 * GitHub:https://github.com/rston
 * 项目:lseek函数及共享文件
 * 功能:1个进程重复打开同一个文件读取。
 */
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>
#include <stdlib.h>

int main(int argc, char **argv)
{
    int fd1 = -1, fd2 = -1;                     // fd即file descriptor,文件描述符     
    int ret = -1;                               // 用于接收read返回值,判断是否读文件成功
    char buf[100] = {0};                        // 构建缓冲区存放从文件读出的内容

    fd1 = open("test.txt", O_RDONLY);
    fd2 = open("test.txt", O_RDONLY);
    if ((-1 == fd1) || (-1 == fd2))             // 判断文件打开是否成功,也可这样写if (fd < 0)
    {
        perror("open file error");              // 通过perror捕捉errno错误信息并打印输出
        exit(-1);
    }

    while (1)
    {
        memset(buf, 0, sizeof(buf));
        ret = read(fd1, buf, 2);                // 读取文件内容
        if (ret < 0)                            // 判断读取文件是否成功
        {
            perror("read file error");
            exit(-1);
        }
        else
        {
            printf("the content of read from fd1 is [%s].\\n", buf);
        }

        sleep(1);

        memset(buf, 0, sizeof(buf));
        ret = read(fd2, buf, 2);                // 读取文件内容
        if (ret < 0)                            // 判断读取文件是否成功
        {
            perror("read file error");
            exit(-1);
        }
        else
        {
            printf("the content of read from fd2 is [%s].\\n", buf);
        }
    }

    close(fd1);                                 // 关闭文件
    close(fd2);

    exit(0);
}

4.multi_write
/*
 * 公司:XXXX
 * 作者:Rston
 * 博客:http://blog.csdn.net/rston
 * GitHub:https://github.com/rston
 * 项目:lseek函数及共享文件
 * 功能:1个进程重复打开同一个文件写入。
 */
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>
#include <stdlib.h>

int main(int argc, char **argv)
{
    int fd1 = -1, fd2 = -1;                     // fd即file descriptor,文件描述符     
    int ret = -1;                               // 用于接收read返回值,判断是否读文件成功

    // 若文件存在则原来的内容消失,若文件不存在则以权限666创建该文件
#if 0   
    fd1 = open("test.txt", O_RDWR | O_TRUNC | O_CREAT , 0666);              // 分别写
    fd2 = open("test.txt", O_RDWR | O_TRUNC | O_CREAT , 0666);
#else
    fd1 = open("test.txt", O_RDWR | O_TRUNC | O_CREAT | O_APPEND, 0666);    // 接续写
    fd2 = open("test.txt", O_RDWR | O_TRUNC | O_CREAT | O_APPEND, 0666);
#endif
    if ((-1 == fd1) || (-1 == fd2))             // 判断文件打开是否成功,也可这样写if (fd < 0)
    {
        perror("open file error");              // 通过perror捕捉errno错误信息并打印输出
        exit(-1);
    }

    while (1)
    {
        ret = write(fd1, "ab", 2);              // 写内容到文件中
        if (ret < 0)                            // 判断内容是否成功写入文件
        {
            perror("write file errot");
            exit(-1);
        }
        else
        {
            printf("the content of write from fd1 is [%s].\\n", "ab");
        }

        sleep(1);

        ret = write(fd2, "cd", 2);              // 写内容到文件中
        if (ret < 0)                            // 判断内容是否成功写入文件
        {
            perror("write file errot");
            exit(-1);
        }
        else
        {
            printf("the content of write from fd2 is [%s].\\n", "cd");
        }
    }

    close(fd1);                                 // 关闭文件
    close(fd2);

    exit(0);
}

以上是关于4.lseek函数及共享文件的主要内容,如果未能解决你的问题,请参考以下文章

逆向及Bof基础实践

20155201 李卓雯 《网络对抗技术》实验一 逆向及Bof基础

20155307刘浩《网络对抗》逆向及Bof基础

20155311高梓云《网络对抗》逆向及Bof基础

20145301赵嘉鑫《网络对抗》逆向及Bof基础

链接器链接过程及相关概念解析