(一)Linux系统编程之文件、目录操作

1.复制文件描述符

#include

int dup(int oldfd);

oldfd-要复制的文件描述符

返回值:新的文件描述符
dup调用成功:
有两个文件描述符指向同一个文件
返回值:取最小的且没被占用的文件描述符,若错误返回-1,错误代码存入errno中。

为什么需要复制一个新的文件描述符?
一个原因是使用fdopen。fclose关闭传递给它的文件描述符fdopen,就是说如果你不希望原始文件描述符被关闭,你必须复制dup;
复制的本质是某些数据结构的共享。

dup()用来复制参数oldfd所指的文件描述词,并将它返回。此新的文件描述词和参数oldfd指的是同一个文件,共享所有的锁定、读写位置和各项权限或flags。例如,当利用lseek()对某个文件描述词作用时,另一个文件描述词的读写位置也会随着改变。不过,文件描述词之间并不共享close-on-exec旗标。

#include 
#include 
#include 
#include 
int main(int argc,char* argv[]){ 

}

int dup2(int oldfd,int newfd);

oldfd->hello
newfd->world

假设newfd已经指向了一个文件,首先断开close与那个文件的链接,newfd指向oldfd指向的文件

(1)文件描述符重定向
(2)oldfd和newfd指向同一个文件

newfd没有被占用,newfd指向oldfd指向的文件

dup2与dup区别时dup2可以用参数newfd指定新文件描述符的数值。若参数newfd已经被使用,则系统就会将newfd所指的文件关闭;若newfd==oldfd,则返回newfd,而不关闭newfd所指的文件。dup2所复制的文件描述符与原来的文件描述符共享各种文件状态。共享所有的锁定,读写位置和各项权限或flags等。

返回值:调用成功则返回新的文件描述符,出错则返回-1;

2.改变已经打开的文件的属性

fcntl

fcntl系统调用可以用来对已打开的文件描述符进行各种控制操作以改变已打开的各种属性

file control

  • 变参函数

  • 复制一个已有的文件描述符
    int ret=fcntl(fd,F_DUPFD);
    dup(old)

  • 获取/设置文件状态标志
    open的状态标志
    (1)获取文件状态标识
    int flag=fcntl(fd,F_GETFL);
    (2)设置文件状态标识
    flag=flag | O_APPEND;//加上一个O_APPEND属性
    fcntl(fd,F_SETFL,flag)

  • 可以更改的几个标识:O_APPEND,O_NONBLOCK

文件记录锁是fcntl函数的主要功能:
记录锁:实现只锁文件的某个部分,并且可以灵活的选择是阻塞方式还是立刻返回方式。

当fcntl用于管理文件记录锁的操作时,第三个参数指向一个struct flock *lock的结构体

O_ACCMODE<0003>:读写文件操作时,用于取出flag的低2位
O_RDONLY<00>:只读打开
O_WRONLY<01>:只写打开
O_RDWR<02>:读写打开

struct flock{
 short_l_type;//锁的类型
 short_l_whence;//偏移量的起始位置:SEEK_SET,SEEK_CUR,SEEK_END
 off_t_l_start;//加锁的起始偏移
 off_t_l_len;//上锁字节
 pid_t_l_pid;//锁的属主进程ID
};
  • short_l_type用来指定设置共享锁(F_RDLCK,读锁)还是互斥锁(F_WDLCK,写锁).

当short_l_type的值为F_UNLCK时,传入函数中将解锁

每个进程都可以在该字节区域设置不同的读锁。
但是,给定的字节上只能设置一把写锁,并且写锁存在就不能再设置其他任何的锁,且该写锁只能被一个进程单独使用。

以上多个进程的情况。

如果是单个进程时,文件的一个区域上只能一把锁,无论是写锁还是读锁。若该区域已经存在一个锁,再在该区域上设置锁时,新锁就会覆盖旧的锁。

l_whence,l_start,l_len三个变量来确定给文件上锁的区域:
(1)l_whence确定文件内部位置的指针从哪开始,I_start确定从l_whence开始的位置的偏移量,两个变量一起确定了文件内的位置指针所先指的位置,即开始上锁的位置,然后l_len的字节数就确定了上锁的区域。

当l_len==0时,则表示锁的区域从起点开始直至最大的可能位置,就是从l_whence和l_start两个变量确定的开始位置开始上锁,将开始以后的所有区域都上锁。

为了锁整个文件,将l_whence,l_start,l_len都设置为0。

  • F_SETLK
    此时,fcntl函数用来设置或释放锁。当short_l_type为F_RDLCK为读锁,F_WDLCK为写锁,F_UNLCK为解锁。

如果锁被其他进程占用,则返回-1;

这种情况设的锁遇到锁被其他进程占用时,会立即停止进程。

  • F_SETLKW
    此时也是给文件上锁,不同于F_SETLK的是,该上锁的方式是阻塞方式。当希望设置的锁因为其他锁而被阻止设置时,该命令会等待相冲突的锁被释放。 当fcntl 的请求不能满足时,就会进入休眠,当请求创建的锁可用时会被信号中断休眠,进程就会被唤醒。

当加锁不成功时,即其上面本来就有规则不允许的条件的锁时,就会出错返回,errno会设置为EACCES或EAGAIN。
EACCES:无存取权限。
EAGAIN:非阻塞下调用阻塞操作。(非阻塞socket编程会遇到。)

  • F_GETLK
    第3个参数lock指向一个希望设置的锁的属性结构,如果锁能被设置,该命令并不真的设置锁,而是只修改lock的l_type为F_UNLCK,然后返回结构体。如果存在一个或多个锁与希望设置的锁相互冲突,则fcntl返回其中的一个锁的flock结构。

判断对应文件的结构体中给定区间中是否有对应结构体中的锁。如果写锁存在,就会阻止我fcntl函数进行,并且会替换给出的结构体指针中的结构体信息,并且在l_pid处返回锁的持有者。

如果写锁不存在,则将_flock.l_type改为F_UNCLK,其余不变

(一)Linux系统编程之文件、目录操作_第1张图片
(图只适用于多进程)

关于获取/设置/释放锁有以下两点需要注意:

1.F_GETLK 和 F_SETLK(或F_SETLKW)并非一个原子操作,所以要视情况而写代码。

2.当你在一块区域中上锁后,如果要释放这块区域中的中间一块区域(不含边界),那么系统会自动将一个锁分成两个锁。

如果再将刚刚释放的区域重新加上本来的锁,系统就会将三个区域合并。

锁无法继承,锁的释放。

锁的继承:

让我们再回顾一下,子进程生成的时候发生了什么?(fork()为例)

1.父进程给子进程分配pid和PCB。

2.复制父进程的环境。

3.给子进程分配地址空间和资源。(vfork() 不执行3,4两步。不建议用,函数不成熟)

4.复制父进程的地址信息。

那么子进程会获得什么?

1.父进程的持有者和允许使用者以及其属性。

2.父进程的环境。

3.父进程分配给其的进程上下文。(比如堆栈信息)。

4.自己独立的一份内存。

5.nice值和进程调度级别,其中进程调度级别在 多级反馈队列中尤为体现。(这是windows和linux下的进程调度置换算法)。

6.根目录和工作目录。

7.打开的文件描述符。(父子进程间沟通关键)。

等…

那么父进程不会给子进程什么?(三点)

1.父进程的父进程ID

2.父进程的阻塞信号和计时器。

3.父进程的文件锁。

OK,那么接下来我们就可以得到结论了:

子进程是无法继承父进程所设置的锁的。

做个很简单的例子,当我父进程给一个文件上写锁后,执行fork(),如果子进程也能继承文件锁,那其也可以在文件中肆意写,这与记录锁的兼容性规则相悖。

锁的释放:

我们都知道,进程通信中IPC对象的持续性分为三种:随进程持续性,随内核持续性,随文件持续性。

记录锁是随进程持续性的IPC对象,锁与进程和文件两方面有关。

即一:当你的进程结束了,其对文件上的所有锁也就都被释放了。

二:当你的文件描述符被关闭了,其上面的锁也就都会被销毁。

这个有点不好理解,并不是文件被关闭,才会销毁锁,即当文件描述符的引用计数在变少时,其上的锁就会被销毁。(前提是这些锁是被执行关闭文件描述符操作的进程上的)

你可能感兴趣的:(Linux系统编程)