您的当前位置:首页正文

文件锁(二)——文件锁的读锁和写锁

2024-11-09 来源:个人技术集锦

文件锁的读锁和写锁

对文件加锁时可以加两种锁,分别是“读文件锁”和“写文件锁”,简称读锁和写锁。

读锁、写锁之间关系

文件的加锁方式

  1. 对整个文件的内容加锁
    对整个文件加锁是最常用的文件加锁方式。
    当你整个文件加锁时,如果文件的长度因为写入新数据或者截短而发生了变化,加锁内容长度会自动变化,保证对内容变化着的整个文件加锁。

  2. 对文件某部分内容加锁

    不过一般来说,对多少内容加锁,就对多少内容解锁。如果你是对整个文件加锁,就将整个文件解锁。

    但是实际上加锁和实际解锁的长度不相同,比如我对1000个字节的内容加了锁,但是只对其中的100字节解锁,不过这种情况用的少,知道怎么回事即可。

    怎么实现文件的整个加锁和区域解锁呢?

    后面再说

  3.  

再看看fcntl的函数原型

int fcntl(int fd, int cmd, ... /* struct flock *flockptr */ );

第三个参数是...,fcntl函数是一个变参函数,第三个参数用不到时就不写

  1. 功能
    fcntl函数有多种功能,我们这里主要介绍实现文件锁的功能,当cmd被设置的是与文件锁相关的宏时,fcntl就是用来实现文件锁。
  2. 参数
    • fd:文件描述符,指向所需要被加锁的文件
    • cmd:实现文件锁时,cmd有三种设置,F_GETLK,F_SETLK和F_SETLKW含义如下:
      1. F_GETLK
        从内核获取文件锁的信息,将其保存到第三个参数,第三个参数struct flock *flockptr,我们这里是要设置文件锁,而不是获取已有的文件锁信息,我们这里用不到这个宏。
      2. F_SETLK
        设置第三个参数所代表的文件锁,而且设置的是非阻塞文件锁,也就是如果加锁失败不会阻塞。也就是说加锁失败后如果不想阻塞的话,就由F_SETLK宏来设置。
        此时,需要用到第三个参数:struct flock *flockptr;
        使用举例:
        1. 第一步:定义一个struct flock flockptr结构体变量(这个结构体变量就是文件锁)。
        2. 第二步:设置flockptr的成员,表示你想设置怎样的文件锁
        3. 第三步:通过第三个参数,将设置好的flockptr的地址传递给fcntl,设置你想要的文件锁
      3. F_SETLKW
        F_SETLKW一样,只不过设置的是阻塞文件锁,也就是说加锁不成功的话就阻塞,是由F_SETLKW宏来决定的。
  3. int fcntl(int fd, int cmd, ... /* struct flock *flockptr */ );
    • 第三个参数
      • 第三个参数设置为什么视情况而定,如果fcntl用于实现文件锁的话,第三个参数为struct flock *flockptr,flockptr代表的就是文件锁。
      • 对flockptr的成员设置为特定的值,就可以将文件锁设置为你想要的锁。
      • struct flock结构体如下:
         struct flock {
                       ...
                       short l_type;    /* Type of lock: F_RDLCK, F_WRLCK, F_UNLCK */
                       short l_whence;  /* How to interpret l_start: SEEK_SET, SEEK_CUR, SEEK_END */
                       off_t l_start;   /* Starting offset for lock */
                       off_t l_len;     /* Number of bytes to lock */
                       pid_t l_pid;     /* PID of process blocking our lock
                                           (set by F_GETLK and F_OFD_GETLK) */
                       ...
        };
        
        

成员说明:

  • l_type:锁类型

    • F_RDLCK:读锁(或称共享锁)
    • F_WRLCK:写锁
    • F_UNLCK:解锁
  • l_whence:加锁位置粗定位,设置同lseek的whence

    • SEEK_SET:文件开始处
    • SEEK_CUR:文件当前位置处
    • SEEK_END:文件末尾位置处
    • l_whence这个与lseek函数的whence是一个含义
  • l_start:精定位,相对于l_whence的偏移,与lseek的offset的含义完全一致
    通过l_whence和l_start的值,就可以用来指定从文件的什么位置开始加锁,不过一般来说,我们会将l_whence指定为SEEK_SET,l_start指定为0,表示从整个文件头上开始加锁。

  • l_len:从l_whence和l_start所指定的起始地点算起,需要对文件多长的内容加锁。

    如果 l_len被设置0,表示一直加锁到文件结尾,如果文件长度时变化的,将自动调整加锁的末尾位置

    l_whence和l_start设置为SEEK_SET和0,然后再将l_len设置为0,就表示从文件加锁到文件末尾,其实就是对整个文件加锁。

    如果只是对文件中间的某段加锁,这只是区域加锁,加区域锁时可以给文件n多个的独立区域加锁。

  • l_pid:当前正加锁进程的PID

代码演示

使用文件锁的互斥操作,解决父子进程向同一文件写"hello",“world\n”时,hello hello world相连的问题。

头文件,学到了封装函数的思想,尤其是使用宏来实现不同类型的文件锁

/* Too many parameters ,Different types of locks
 * Write dead!!!!!!!!!
 * */
/* Set non blocking write lock */
#define SET_WRFLCK(fd, l_whence, l_start, l_len) \
		set_filelock(fd, F_SETLK, F_WRLCK, l_whence, l_start, l_len)

/* Set blocking write lock */
#define SET_WRFLCKW(fd, l_whence, l_start, l_len) \
		set_filelock(fd, F_SETLKW, F_WRLCK, l_whence, l_start, l_len)

/* Set blocking read lock */
#define SET_RDFLCKW(fd, l_whence, l_start, l_len) \
		set_filelock(fd, F_SETLKW, F_RDLCK, l_whence, l_start, l_len)

/* Set nonblocking read lock */
#define SET_RDFLCK(fd, l_whence, l_start, l_len) \
		set_filelock(fd, F_SETLK, F_RDLCK, l_whence, l_start, l_len)

/* Unlock */
#define SET_UNFLCK(fd, l_whence, l_start, l_len) \
		set_filelock(fd, F_SETLK, F_UNLCK, l_whence, l_start, l_len)

/*Package and set the locking function
 * */
static void set_filelock(int fd, int ifwait, short l_type, short l_whence, off_t l_start, short l_len) 
{
	int ret = 0;
	struct flock flock;

	flock.l_type = l_type;
	flock.l_whence = l_whence;
	flock.l_start = l_start;
	flock.l_len = l_len;

	ret = fcntl(fd, ifwait, &flock);
	if (ret == -1)
	{
		perror("fcntl");
		exit(EXIT_FAILURE);
	}
}

int main(int argc, char *argv[])
{
	int fd = 0;
	int ret = 0;

	fd = open("./hello", O_RDWR|O_CREAT|O_TRUNC, 0664);
	if (fd == -1)  print_err("./hello", __LINE__, errno); 

	ret = fork();
	if (ret > 0)
	{
		while(1)
		{
			SET_WRFLCKW(fd, SEEK_SET, 0, 0);
			write(fd, "hello ", 6);
			write(fd, "world\n", 6);
			SET_UNFLCK(fd, SEEK_SET, 0, 0);
		}
	}
	else if (ret == 0)
	{
		while(1)
		{
			SET_WRFLCKW(fd, SEEK_SET, 0, 0);
			write(fd, "hello ", 6);
			write(fd, "world\n", 6);
			SET_UNFLCK(fd, SEEK_SET, 0, 0);
		}
	}
	return 0;
}

在hello文件里面使用/hello hello没找到文本内容说明成功了。

对于fcntl的认识不仅仅可以追加改变文件标志,还可以实现文件锁的功能

显示全文