系统级I/O中的共享文件

共享文件

程序员可以使用许多不同的方式来共享Linux文件。除非你很清楚内核是如何表示打开的文件,否则文件共享的概念相当难懂。内核用三个相关的数据结构来表示打开的文件:

  • 描述符表(descriptor table)。每个进程都有它独立的描述符表,它的表项是由进程打开的文件描述符来索引的。每个打开的描述符表项指向文件表中的一个表项。
  • 文件表(file table)。打开文件的集合是由一张文件表来表示的,所有的进程共享这张表。每个文件表的表项组成(针对我们的目的)包括当前的文件位置、引用计数(reference count)(即当前指向该表项的描述符表项数),以及一个指向v-node表中对应表项的指针。关闭一个描述符会减少相应的文件表表项中的引用计数。内核不会删除这个文件表表项,直到它的引用计数为零。
  • v-node表。同文件表一样,所有的进程共享这张v-node表。每个表项包含stat结构中的大多数信息,包括st_mode和st_size成员。

接下来展示一些示例:
首先一些有用的头文件放出来:

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <ctype.h>
#include <setjmp.h>
#include <signal.h>
#include <sys/time.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <sys/mman.h>
#include <errno.h>
#include <math.h>
#include <pthread.h>
#include <semaphore.h>
#include <sys/socket.h>
#include <netdb.h>
#include <netinet/in.h>

接下来是一些需要自建的函数:

//error函数
void unix_error(char *msg)
{
fprintf(stderr,"%s:%s\n",msg,strerror(errno));
exit(0);
}

//Fork函数
pit_t Fork(void)
{
pid_t pid;

if((pid==fork())<0)
unix_error("Fork error");
return pid;
}

//Wait函数
pid_t Wait(int *status)
{
    pid_t pid;

    if ((pid  = wait(status)) < 0)
     unix_error("Wait error");
     return pid;
} 

//Waitpid函数
pid_t Waitpid(pid_t pid, int *iptr, int options) 
{
    pid_t retpid;

    if ((retpid  = waitpid(pid, iptr, options)) < 0)
    unix_error("Waitpid error");
    return(retpid);
}

//Open函数
int Open(const char *pathname, int flags, mode_t mode)
{
    int rc;

    if ((rc = open(pathname, flags, mode))  < 0)
    unix_error("Open error");
    return rc;
}

//Read函数
ssize_t Read(int fd, void *buf, size_t count) 
{
    ssize_t rc;

    if ((rc = read(fd, buf, count)) < 0) 
    unix_error("Read error");
    return rc;
}

//Write函数
ssize_t Write(int fd, const void *buf, size_t count) 
{
    ssize_t rc;

    if ((rc = write(fd, buf, count)) < 0)
    unix_error("Write error");
    return rc;
}

//Close函数
void Close(int fd)
{
    int rc;

    if ((rc = close(fd)) < 0)
    unix_error("Close error");
}

//Dup2函数
int Dup2(int fd1, int fd2) 
{
    int rc;

    if ((rc = dup2(fd1, fd2)) < 0)
    unix_error("Dup2 error");
    return rc;
}

(真的要吐槽一下这个博客,就因为一个文本编辑器,搞得我只能一行一行地复制代码😭)

咳咳。。。接下来开始举例子:
首先先写一个小文本foobar.txt
内容就敲foobar六个字母
然后开始敲代码:
示例1:

int main()
{
    int fd1, fd2;
    char c;
    fd1=Open("foobar.txt",O_RDONLY,0);
    fd2=Open("foobar.txt",O_RDONLY,0);
    Read(fd1,&c,1);
    Read(fd2,&c,1);
    printf("c=%c\n",c);
    exit(0);
}    

好的我看出来了满满的套路,那么不出意外,它的结果是这样的:
在这里插入图片描述
这个时候这张图就有用了:
在这里插入图片描述
(请忽略我那潦草得不能再潦草的字谢谢。。。)
这个图所展示的就是典型的打开文件的内核数据结构。在这个示例中,两个描述符引用同一个文件。没有共享。(要我说就相当于双胞胎,复制粘贴,各管各的。。。)

示例2:(好我又要努力敲代码了。。。)

int main()
{
    int fd;
    char c;
    fd=Open("foobar.txt",O_RDONLY,0);
    if(Fork==0){
    Read(fd,&c,1);
    exit(0);
    }
    Wait(NULL);
    Read(fd,&c,1);
    printf("c=%c\n",c);
    exit(0);

好的相信你也看出来了这又是一个满满的套路,结局应该也不出你所料:
在这里插入图片描述
这个时候我又可以搬出一张差不多的原理图:
在这里插入图片描述
是的这张图呢就展示了子进程如何继承父进程的打开文件。
初始状态是上一张图哦。)(那么呢我的理解就是。。。父亲为了等等儿子走了跟儿子一样的老路。。。逻辑好像。。)其实这个代码很多弯的,四个函数放在一起非常容易乱套,但其实就是儿子把第一个字母拿走了,父亲来了就只能拿第二个了嘻嘻🤭

示例3:(这里使用了重定向

int main()
{
    int fd1, fd2;
    char c;
    fd1=Open("foobar.txt",O_RDONLY,0);
    fd2=Open("foobar.txt",O_RDONLY,0);
    Read(fd2,&c,1);
    Dup2(fd2,fd1);
    Read(fd1,&c,1);
    printf("c=%c\n",c);
    exit(0);
}
  

结果是啥?你猜
在这里插入图片描述
是的我又要放图了:
在这里插入图片描述这张图是指:通过调用Dup2(4,1)重定向标准输出后的内核数据结构。
意思就是文件4把文件1覆盖了,文件1在这个函数启动后就是文件4本4。所以上面的函数就是读了两次文件fd2,so输出为o。

哦对了,有一些宏集体放在这里解释一下:
O_RDONLY:只读。
O_WRONLY:只写。
O_RDWR:可读可写。

接下里做一些扩展练习吧:
扩展训练1:

int main(int argc, char *argv[])
{
    int fd1, fd2, fd3;
    char c1,c2,c3;
    char *fname = argv[1];
    fd1 = Open(fname, O_RDONLY, 0);
    fd2 = Open(fname, O_RDONLY, 0);
    fd3 = Open(fname, O_RDONLY, 0);
    dup2(fd2, fd3);

    Read(fd1, &c1, 1);
    Read(fd2, &c2, 1);
    Read(fd3, &c2, 1);
    printf("c1 = %c, c2 = %c, c3 = %c\n", c1, c2, c3);

    Close(fd1);
    Close(fd2);
    Close(fd3);
    return 0;
}    

如果你看懂了之前的代码,这里也就很简单了
在这里插入图片描述
(后面的txt文件是需要和代码放在一个目录下面要读取的文件,内容与文件同名)

扩展训练2:

int main(int argc, char *argv[])
{
    int fd1;
    int s = getpid() & 0x1;
    char c1,c2;
    char *fname = argv[1];
    fd1 = Open(fname, O_RDONLY, 0);
    Read(fd1, &c1, 1);
    if (fork()) {
    sleep(s);
    Read(fd1, &c2, 1);
    printf("Parent: c1 = %c, c2 = %c\n", c1, c2);
    }else{
    sleep(1-s);
    Read(fd1, &c2, 1);
    printf("Child: c1 = %c, c2 = %c\n", c1, c2);
    }
    return 0;
}

这个跟之前不一样的是多了一个sleep函数,手动睡眠了一小会哈哈
在这里插入图片描述
扩展训练3:

int main(int argc, char *argv[])
{
    int fd1, fd2, fd3;
    char *fname = argv[1];
    fd1 = Open(fname, O_CREAT|O_TRUNC|O_RDWR, S_IRUSR|S_IWUSR);
    Write(fd1, "pqrs", 4);
    fd3 = Open(fname, O_APPEND|O_WRONLY, 0);
    Write(fd3, "jklmn", 5);
    fd2 = dup(fd1);  /* Allocates new descriptor */
    Write(fd2, "wxyz", 4);
    Write(fd3, "ef", 2);
    Close(fd1);
    Close(fd2);
    Close(fd3);
    return 0;
}

现在把它跑一下:在这里插入图片描述
然后再看看文档(懒得再贴图了,直接把文档内容附上:)
pqrswxyznef

现在把多出来的几个宏定义告诉你:
O_CREAT:如果文件不存在,就创建它的一个截断的(空)文件。
O_TRUNC:如果文件已经存在,就截断它。(即如果是一个可写的普通文件,则清空它)
O_APPEND:在每次写操作前,设置文件位置到文件的结尾处。

以及函数的最后一个参数,访问权限位
S_IRUSR:使用者(拥有者)能够读这个文件。
S_IWUSR:使用者(拥有者)能够写这个文件。

以及dup函数的字面解释:
dup用来复制参数oldfd所指的文件描述符。当复制成功时,返回最小的尚未被使用过的文件描述符,若有错误则返回-1.错误代码存入errno中返回的新文件描述符和参数oldfd指向同一个文件,这两个描述符共享同一个数据结构,共享所有的锁定,读写指针和各项全现或标志位。

这里有点麻烦,以我的语言功底没办法说清楚,,以我的理解就是,在使用这个函数之后,fd2的文件描述符为4。但是在给fd2这个文件写“wxyz”时,覆盖了之前写的“jklmn”,但由于之前是五个字节,所以n保留了,然后结果就是这个亚子。。。
说不出为啥,但我觉得我肯定有地方理解错了,,如果可以的话希望大神们帮忙解释解释。。谢谢🙏🙏

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值