Linux系统的文件定位、共享操作

lseek函数

  • lseek(lseek函数用于改变文件的当前偏移量。)
  • 头文件
    #include<unistd.h>
  • 定义函数
    off_t lseek(int filedes, off_t offset, int origin);
  • 函数说明
    filedes 文件描述符
    offset 必须与origin一同解析
    origin为 SEEK_SET, 则offset从文件的开头算起。
    origin为 SEEK_CUR, 则offset从当前位置算起,既新偏移量为当前偏移量加上offset
    origin为 SEEK_END, 则offset从文件末尾算起。
  • 返回值
    如果失败,返回值为-1,成功返回移动后的文件偏移量。可用lseek确定文件当前偏移量:currpos = lseek( fd, 0, SEEK_CUR)
  • lseek常用于找到文件的开头、找到文件的末端,判定文件描述符的当前位置
  • lseek仅将当前的文件偏移量记录在内核中,它并不引起任何I/O操作,然后该偏移量用于下一次读、写操作。
  • 文件偏移量可以大于文件的当前长度,但并不改变相应的i节点信息。在这种情况下的下一次写将延长该文件,并在文件中构成一个空洞,但文件大小并不是文件的最大偏移量。对空洞位置的读操作将返回0。
//lseek实现空洞
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <errno.h>
#include <stdlib.h>

int main()
{
	int fd;
	int ret;
	fd = open("hole.txt",O_WRONLY|O_CREAT|O_TRUNC,0644);
	if(fd == -1)
		exit(1);
		//perror("lseek error");
	write(fd,"hello",5);
	ret = lseek(fd,1024*1024*1024,SEEK_CUR);
	if(ret == -1)
		exit(2);
		//perror("lseek error");
	write(fd,"world",5);
	close(fd);
	return 0;
}
  • 执行上述代码结果:
    在这里插入图片描述

pread函数

  • pread(在给定的偏移量读取一个文件描述符)
  • 头文件
    #include<unistd.h>
  • 函数原型
    ssize_t pread (int fd, void *buf, size_t count, off_t pos)
  • 返回值
    返回读到的字节数;出错:返回-1;到文件结尾:返回0
  • 解决问题
    由于lseek和read 调用之间,内核可能会临时挂起进程,所以对同步问题造成了问题,调用pread相当于顺序调用了lseek 和read,这两个操作相当于一个捆绑的原子操作
  • 其他
    调用pread时,无法中断其定位和读操作,另外不更新文件指针

pwrite函数

  • pwrite(在给定的偏移量写入一个文件描述符)
  • 头文件
    #include<unistd.h>
  • 函数原型
    ssize_t pwrite (int fd, const void *buf, size_t count, off_t pos )
  • 返回值
    返回已写的字节数;出错:返回-1
  • 解决问题
    由于lseek和write 调用之间,内核可能会临时挂起进程,所以对同步问题造成了问题,调用pwrite相当于顺序调用了lseek 和 write,这两个操作相当于一个捆绑的原子操作
  • 其他
    调用pwrite时,无法中断其定位和读操作,另外不更新文件指针

与read/write的区别

  1. 调用更容易使用,特别是在进行需要技巧的操作时
  2. 完成工作后不会改变文件指针
  3. 避免使用lseek时可能造成的竞争条件
  4. 如果有多个线程共享文件描述符,当第一个线程调用lseek之后,在它进行读取或写入操作之前,同一个程序中的另一个线程可能会改变文件的位置

文件共享

  • UNIX/Linux支持不同进程间共享文件。内核使用的三种表(文件描述符表、文件表、索引结点表)之间的关系决定了在文件共享方面一个进程对另一个进程可能产生的影响
  • 每个进程在进程表中有一个文件描述符表,每个描述符表项指向一个文件表
  • 内核为每一个被打开的文件维护一张文件表,文件表项包含
    • 文件的状态标志(读、写、同步、非阻塞)
    • 文件当前位置
    • 指向该文件索引节点表的指针
  • 每个文件(或设备)都有一个索引节点,它包含了文件类型属性及文件数据
  • 如果两个进程分别打开同一个的文件(物理文件),则它们有不同的文件表,因此每个进程有自己的文件当前位置,因此其读写操作互不影响。
  • 也存在不同进程共享同一个文件表(父子进程),或同一进程共享同一个文件表(dup操作)。此时,两个进程对该文件的读写操作将基于同一个文件当前位置。

不同进程共享相同文件

在这里插入图片描述
在这里插入图片描述

同一进程共享相同文件

在这里插入图片描述
在这里插入图片描述

dup和dup2

  • 头文件:unistd.h
  • 函数原型
    int dup( int oldfd );
    int dup2( int oldfd, int targetfd );
  • 函数说明:复制一个文件的描述符,dup2函数跟dup函数相似,但dup2函数允许调用者规定一个有效描述符和目标描述符的
  • 返回值:dup2函数成功返回时,目标描述符
  • 示例
int fd1, fd2;
...
fd2 = dup( fd1 );
int oldfd;
oldfd = open("app_log", (O_RDWR | O_CREATE), 0644 );
dup2( oldfd, 1 );	//1代表stdout
close( oldfd );

线程共享文件

  • 线程的定义:有时称轻量级进程,是进程中的一个执行线路或线索,是一个相对独立的、可独立调度和指派的执行单元
  • 线程的创建:应用程序可以通过一个统一的clone()系统调用接口,用不同的参数指定创建轻量进程还是普通进程。
  • clone()调用do_fork()创建线程, do_fork()参数为:
    (CLONE_VM | CLONE_FS | CLONE_FILES | CLONE_SIGHAND)
    //其中do_fork()为fork()和vfork()的底层操作。
  • CLONE_VM:do_fork()需要调用copy_mm()来设置task_struct中的mm和active_mm项,这两个mm_struct数据与进程所关联的内存空间相对应。如果do_fork()时指定了CLONE_VM开关,copy_mm()将把新的task_struct中的mm和active_mm设置成与current的相同,同时提高该mm_struct的使用者数目
    (mm_struct::mm_users)。也就是说,轻量级进程与父进程共享内存地址空间。
  • CLONE_FS:task_struct中利用fs(struct fs_struct *)记录了进程所在文件系统的根目录和当前目录信息,do_fork()时调用copy_fs()复制了这个结构;而对于轻量级进程则仅增加fs->count计数,与父进程共享相同的fs_struct。也就是说,轻量级进程没有独立的文件系统相关的信息,进程中任何一个线程改变当前目录、根目录等信息都将直接影响到其他线程。
  • CLONE_FILES:一个进程可能打开了一些文件,在进程结构task_struct中利用files(struct files_struct *)来保存进程打开的文件结构(struct file)信息,do_fork()中调用了copy_files()来处理这个进程属性;轻量级进程与父进程是共享该结构的,copy_files()时仅增加files->count计数。这一共享使得任何线程都能访问进程所维护的打开文件,对它们的操作会直接反映到进程中的其他线程。
  • CLONE_SIGHAND:每一个Linux进程都可以自行定义对信号的处理方式,在task_struct中的sig(struct signal_struct)中使用一个struct k_sigaction结构的数组来保存这个配置信息,do_fork()中的copy_sighand()负责复制该信息;轻量级进程不进行复制,而仅仅增加signal_struct::count计数,与父进程共享该结构。也就是说,子进程与父进程的信号处理方式完全相同,而且可以相互更改。
  • 总结:线程间所有文件结构都为共享资源,不但“文件表项”(file对象)是共享的,就连“文件描述符表”(files_struct结构)也是共享的。线程的创建仅仅增加的是files和fs的引用计数,“文件打开计数”(file对象的引用计数)并没有增加,所以任何一个线程对打开的文件执行close操作,文件都将关闭(文件打开计数为1的情况)。但是如果线程不进行打开文件的关闭,则文件直到进程结束时才会关闭,这就是使用多线程实现tcp服务器时,服务线程必须要显示调用close的原因,否则永远不会发送FIN终止链接(因为主线程一直处于监听不会结束)。

进程间文件描述符的传递

◼ 传递描述符的函数的参数是fd,fd是打开文件指针在数组中的下标
◼ 将一个文件描述符传递给另一个进程后,文件的“访问计数”会增加
◼ 进程间传递文件描述符可以看做跨进程的dup调用,也就是同一个file对象在不同进程间的映射
◼ 对于网络接口返回的描述符 ,只能采取传递文件描述符的方法。
◼ UNIX系统中两个方法:BSD sendmsg,recvmsg方法;SYSV ioctl方法
◼ 进程间传递文件描述符时,发送进程和接收进程共享同一文件表项
◼ 进程间文件描述符的传递,只是通过内核将接收文件的一个新file指针指向和发送进程的同一个file对象,并使这个file对象的引用计数增加
在这里插入图片描述

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值