目录
1. 背景
2. 相关知识储备
思路一: 民科 mtime 档案最后修改时间
思路二: 科班 作业系统通知特性, 例如 linux 的 inotify
3. 相关代码设计
3.1 简单实用版
3.2 尝试多执行绪
3.3 多执行绪版本
4. 总结
正文
1. 背景
组态档动态重绘这个业务场景非常常见. 存在两个主要使用场景, 客户端和服务器.
客户端需求很直白, 我本地配置变更, 程序能及时和非及时的重刷到系统中.
服务器相比客户端做法要多些环节, 服务器本地会有一份配置兜底, 配置中心中配置发生改变会推送给触发给服务器触发内部更新操作.
我们这里主要聊场景偏向于客户端, 本地配置发生改变, 我们如何来更新存储器中配置.
文章承接于: C中级 - 档案辅助操作
2. 相关知识储备
首先思考一个问题我们如何判断一个档案发生了更新 ?
这里提供两种思路.
思路一: 民科 mtime 档案最后修改时间
struct stat { unsigned long st_dev; /* Device. */ unsigned long st_ino; /* File serial number. */ unsigned int st_mode; /* File mode. */ unsigned int st_nlink; /* Link count. */ unsigned int st_uid; /* User ID of the file's owner. */ unsigned int st_gid; /* Group ID of the file's group. */ unsigned long st_rdev; /* Device number, if device. */ unsigned long __pad1; long st_size; /* Size of file, in bytes. */ int st_blksize; /* Optimal block size for I/O. */ int __pad2; long st_blocks; /* Number 512-byte blocks allocated. */ long st_atime; /* Time of last access. */ unsigned long st_atime_nsec; long st_mtime; /* Time of last modification. */ unsigned long st_mtime_nsec; long st_ctime; /* Time of last status change. */ unsigned long st_ctime_nsec; unsigned int __unused4; unsigned int __unused5; };
在结构体中 st_atime, st_mtime, st_ctime 栏位可以知道, Linux 档案有三个时间属性:
1. mtime: 档案内容最后修改时间
2. ctime: 档案状态改变时间, 如权限, 属性被更改
3. atime: 档案内容被访问时间
如 cat, less 等 在默认情况下, ls 显示出来的是该档案的 mtime, 即档案内容最后修改时间.
如果你需要查看另外两个时间, 可以使用 ls -l --time ctime 命令.
思路二: 科班 作业系统通知特性, 例如 linux 的 inotify
man inotify
inotify_init1 -> inotify_add_watch IN_MODIFY / inotify_rm_watch -> poll 监控机制 -> close
IN_MODIFY (+) File was modified (e.g., write(2), truncate(2)).
流程去注册关注修改时间, 当档案发生修改时候作业系统会通知上层应用具体修改详情.
linux inotify 是一种档案变化通知机制, 它是一个内核用于通知用户空间程序档案系统变化的机制,
以便用户态能够及时地得知内核或底层硬设备发生了什么.
我们这里采用民科思路. linux inotify 对于我们场景有点大材小用了. 欢迎感兴趣人参照官方例子去尝试.
3. 相关代码设计
3.1 简单实用版
素材:
https://github.com/wangzhione/structc/blob/9de5200229845c4c7acf921ca63c794918b28fe5/modular/system/file.h
https://github.com/wangzhione/structc/blob/9de5200229845c4c7acf921ca63c794918b28fe5/modular/system/file.c
业务能力设计 file_set 注册和洗掉, file_update 触发检查和更新操作
#pragma once #include "struct.h" #include "strext.h" // // file_f - 档案更新行为 // typedef void (* file_f)(FILE * c, void * arg); // // file_set - 档案注册更新行为 // path : 档案路径 // func : NULL 标记清除, 正常 update -> func(path -> FILE, arg) // arg : func 额外自变量 // return : void // extern void file_set(const char * path, file_f func, void * arg); // // file_update - 组态档重绘操作 // return : void // extern void file_update(void);
具体思路是利用 list + mtime , 可以观察 struct 设计部分
#include "file.h" struct file { time_t last; // 档案最后修改时间点 char * path; // 档案全路径 unsigned hash; // 档案路径 hash 值 file_f func; // 执行行为 void * arg; // 行为自变量 struct file * next; // 档案下一个结点 }; static struct file * file_create(const char * path, unsigned h, file_f func, void * arg) { assert(path && func); if (fmtime(path) == -1) { RETURN(NULL, "mtime error p = %s", path); } struct file * fu = malloc(sizeof(struct file)); if (NULL == fu) { return NULL; } fu->last = -1; fu->path = strdup(path); if (NULL == fu->path) { free(fu); return NULL; } fu->hash = h; fu->func = func; fu->arg = arg; // fu->next = NULL; return fu; } inline void file_delete(struct file * fu) { free(fu->path); free(fu); } static struct files { struct file * list; // 当前档案物件集 } f_s; // files add static void f_s_add(const char * path, unsigned hash, file_f func, void * arg) { struct file * fu = file_create(path, hash, func, arg); if (fu == NULL) { return; } // 直接插入到头结点部分 fu->next = f_s.list; f_s.list = fu; }
struct file 存盘档案操作物件, struct files 是 struct file list 集合.
3.2 尝试多执行绪
我们知道 file_set 和 file_update 不是执行绪安全的. 依赖业务系统启动时候统一呼叫 file_set 无法运行时修改相关设定.
不知道是否有同学会采用如下设计
static struct files { atomic_flag lock; struct file * list; } f_s;
通过 lock 来保证执行绪安全.
这种思路确实能解决执行绪安全问题, 存在很多缺陷, file_update 业务上面很耗时, 他会阻塞 file_set 操作, 特殊情况会引发业务雪崩.
所以我们需要更针对性锁.
3.3 多执行绪版本
为了适配多执行绪情况. 首先我们明确下简单业务, 同步的 file list 就够用了.
我们这里单纯为了没事要吃蛋炒饭态度, 构造 file dict hash + atomic lock 来没事找事.
素材:
https://github.com/wangzhione/structc/blob/8c040f0cb3507fc4563bc18f48104f9cc20c5da5/modular/system/file.h
https://github.com/wangzhione/structc/blob/8c040f0cb3507fc4563bc18f48104f9cc20c5da5/modular/system/file.c
总体设计思路
#include "file.h" struct file { time_t last; // 档案最后修改时间点 file_f func; // 执行行为 void * arg; // 行为自变量 }; static struct file * file_create(const char * path, file_f func, void * arg) { assert(path && func); if (fmtime(path) == -1) { RETURN(NULL, "mtime error p = %s", path); } struct file * fu = malloc(sizeof(struct file)); if (NULL == fu) { return NULL; } fu->last = -1; fu->func = func; fu->arg = arg; return fu; } static inline void file_delete(struct file * fu) { free(fu); } struct files { atomic_flag data_lock; // const char * path key -> value struct file // 用于 update 资料 volatile dict_t data; atomic_flag backup_lock; // const char * path key -> value struct file // 在 update 兜底备份资料 volatile dict_t backup; }; static struct files F = { .data_lock = ATOMIC_FLAG_INIT, .backup_lock = ATOMIC_FLAG_INIT, }; extern void file_init() { F.data = dict_create(file_delete); F.backup = dict_create(file_delete); }
我们先在 data 中添加资料, 如果 data 被 update 占用, 我们把资料放入 backup 中再去处理.
// // file_set - 档案注册更新行为 // path : 档案路径 // func : NULL 标识清除, 正常 update -> func(path -> FILE, arg) // arg : func 额外自变量 // return : void // void file_set(const char * path, file_f func, void * arg) { struct file * fu = NULL; assert(path && *path); // step 1 : 尝试竞争 data lock if (atomic_flag_trylock(&F.data_lock)) { if (NULL != func) { fu = file_create(path, func, arg); } dict_set(F.data, path, fu); return atomic_flag_unlock(&F.data_lock); } // step 2 : data lock 没有竞争到, 直接竞争 backup lock atomic_flag_lock(&F.backup_lock); fu = file_create(path, func, arg); dict_set(F.backup, path, fu); atomic_flag_unlock(&F.backup_lock); }
4. 总结
去感受其中思路. 我用C写代码很顺手. 但有时候觉得 C 在现在阶段, 不是专业吃这个饭的,
可以尝试用其它更加高级语言来轻松快捷表达自己的想法和工程版本.
对于开发生涯我花了很多年找到自己定位, 我的底层核心是一名软件工程师. 然后语言和技术以及商业工程问题陆续通顺起来了.
(因为我的单元测验不充分, 错误可能很多, 欢迎在 github 给我提 commit or issure. 时间愉快)
0 评论