0%

文件锁-sysrepo笔记(5)

背景

sysrepo是基于共享内存的数据库,实际共享内存的外在体现是一个共享的文件。对这个共享文件的访问控制又是通过文件锁的方式来实现的。因为sysrepo只是一个库而已,会被多个进程链接,共享内存文件的路径都是一致的,在编译的时候确定了的。多进程间的资源互斥这里使用的就是文件锁的方式。

实现

sysrepo对数据的存储主要分为两块,一个是main shm,用来保存概要的数据sr_main_shm_t和模块的信息数组sr_mod_t(大部分保存模块对应信息的一个偏移)。另一个是ext shm,用来保存具体的模块对应的数据信息。

其中对main shm的资源资源控制使用的是一个sr_main的文件锁来保护。同样的,对ext shm的资源资源控制使用的是一个sr_ext的文件锁来保护。

今天主要介绍一下main shm的文件锁sr_main的控制。

接口

涉及的锁接口主要有三个:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26

/**
* @brief Create main SHM file lock used for creating main SHM.
*
* @param[out] shm_lock SHM create lock file descriptor.
* @return err_info, NULL on success.
*/
sr_error_info_t *sr_shmmain_createlock_open(int *shm_lock);

/**
* @brief Lock main SHM file lock. Note that the oldest standard file locks
* are used, which lock for the whole process (every thread).
*
* @param[in] shm_lock Opened SHM create lock file descriptor.
* @return err_info, NULL on success.
*/
sr_error_info_t *sr_shmmain_createlock(int shm_lock);

/**
* @brief Unlock main SHM file lock.
*
* @param[in] shm_lock Locked SHM create lock file descriptor.
*/
void sr_shmmain_createunlock(int shm_lock);


这里shm_lock实际上是锁文件的文件描述符fd。

锁文件创建

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27

sr_error_info_t *
sr_shmmain_createlock_open(int *shm_lock)
{
sr_error_info_t *err_info = NULL;
char *path;
mode_t um;

if (asprintf(&path, "%s/%s", sr_get_repo_path(), SR_MAIN_SHM_LOCK) == -1) {
SR_ERRINFO_MEM(&err_info);
return err_info;
}

/* set umask so that the correct permissions are really set */
um = umask(SR_UMASK);

*shm_lock = open(path, O_RDWR | O_CREAT, SR_MAIN_SHM_PERM);
free(path);
umask(um);
if (*shm_lock == -1) {
SR_ERRINFO_SYSERRNO(&err_info, "open");
return err_info;
}

return NULL;
}

这里获取到文件的路径,设置创建文件属性的掩码,然后使用open打开文件,如果文件不存在,创建文件并使用指定的访问属性。

加锁操作

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23

sr_error_info_t *
sr_shmmain_createlock(int shm_lock)
{
struct flock fl;
int ret;
sr_error_info_t *err_info = NULL;

assert(shm_lock > -1);

memset(&fl, 0, sizeof fl);
fl.l_type = F_WRLCK;
do {
ret = fcntl(shm_lock, F_SETLKW, &fl);
} while ((ret == -1) && (errno == EINTR));
if (ret == -1) {
SR_ERRINFO_SYSERRNO(&err_info, "fcntl");
return err_info;
}

return NULL;
}

对文件加解锁使用了一个结构体struct flock,定义一些文件的锁的选项。还有fcntl这个函数来设置定义的选项。

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

这是一个拥有可变参数的函数声明,filedes自然是要操作的文件描述符,对与记录锁相关的操作,cmd只能是F_GETLK, F_SETLK, 或者 F_SETLKW,而第三个参数则必须是一个指向flock结构体的指针。

1
2
3
4
5
6
7
struct flock {
short l_type;/*F_RDLCK, F_WRLCK, or F_UNLCK */
off_t l_start;/*offset in bytes, relative to l_whence */
short l_whence;/*SEEK_SET, SEEK_CUR, or SEEK_END */
off_t l_len;/*length, in bytes; 0 means lock to EOF */
pid_t l_pid;/*returned with F_GETLK */
};

第一个成员是加锁的类型:只读锁,读写锁,或是解锁。l_startl_whence用来指明加锁部分的开始位置,l_len是加锁的长度,l_pid是加锁进程的进程id。比如说,我们现在需要把一个文件的前三个字节加读锁,则该结构体的l_type=F_RDLCK, l_start=0, l_whence=SEEK_SET, l_len=3,l_pid不需要指定,然后调用fcntl函数时,cmd参数使用F_SETLK.

回到sr_shmmain_createlock函数, 设置了读写锁类型F_WRLCKcmdF_SETLKW,然后就调用fcntl加锁了。

解锁操作

1
2
3
4
5
6
7
8
9
10
11
12

void
sr_shmmain_createunlock(int shm_lock)
{
struct flock fl;

memset(&fl, 0, sizeof fl);
fl.l_type = F_UNLCK;
if (fcntl(shm_lock, F_SETLK, &fl) == -1) {
assert(0);
}
}

这里设置cmdF_SETLK, l_type类型为解锁。

demo编程

我写了一个demo程序,2个程序分别往一个共享内存写数据,对一个数进行累加。一个步进1, 加10次。 一个步进2,加10次。当两个程序结束后,都延迟10s钟,分别再读取数据,最终的结果应该是我们认为的30。

文件锁

demo代码路径:

https://github.com/fishmwei/blog_code/tree/master/sysrepo/filelock

行动,才不会被动!

欢迎关注个人公众号 微信 -> 搜索 -> fishmwei,沟通交流。