一、介绍
#
1. 提供的系统调用
#
1SYSCALL_DEFINE1(inotify_init1, int, flags)
2SYSCALL_DEFINE0(inotify_init)
3SYSCALL_DEFINE3(inotify_add_watch, int, fd, const char __user *, pathname, u32, mask)
4SYSCALL_DEFINE2(inotify_rm_watch, int, fd, __s32, wd)
2. 示例用法
#
1class FileMonitor final {
2public:
3 explicit FileMonitor(std::string path) : m_filePath(std::move(path)) {}
4
5 ~FileMonitor() { stop(); }
6
7 void start() {
8 stop();
9 std::promise<void> p;
10 m_monitorFuture = std::make_shared<std::future<void>>(std::async(std::launch::async, [&p, this]() {
11 p.set_value();
12 std::string tag = "file(" + m_filePath + ") monitor loop";
13 LOGI(WHAT("{} begin", tag));
14
15 {
16 std::lock_guard<std::mutex> lock(m_fdMutex);
17 // 设置为非阻塞方式
18 m_monitorFD = inotify_init1(IN_NONBLOCK);
19 if (m_monitorFD < 0) {
20 LOGE(WHAT("{} failed", tag),
21 REASON("inotify_init failed, ec: {}",
22 std::to_string(std::error_code(errno, std::system_category()))),
23 WILL("exit thread and won't watch file"));
24 return;
25 }
26 }
27
28 // 添加文件监听的函数,返回false说明总的文件描述符被关闭
29 if (!addFileWatch()) {
30 LOGI(WHAT("{}, stop monitor", tag));
31 return;
32 }
33
34 // 开始监听文件
35 while (true) {
36 char buf[sizeof(struct inotify_event) + m_filePath.size() + 1] = {0};
37 ssize_t len;
38 {
39 std::lock_guard<std::mutex> lock(m_fdMutex);
40 if (m_monitorFD < 0 || m_watchFD < 0) {
41 // 这里说明外部关闭了监听fd,也就是析构了,退出就好
42 break;
43 }
44 // 非阻塞读取event事件
45 len = read(m_monitorFD, buf, sizeof(buf));
46 }
47 if (len < 0) {
48 auto err = errno;
49 // 非阻塞的读不到事件返回EAGAIN,直接continue,其他的报错continue
50 if (err != EAGAIN) {
51 LOGW(WHAT("{}, read get err", tag),
52 REASON("get error {}", std::to_string(std::error_code(err, std::system_category()))),
53 WILL("retry after 10 ms"));
54 }
55 std::this_thread::sleep_for(std::chrono::milliseconds(10));
56 continue;
57 }
58
59 if (len < sizeof(struct inotify_event)) {
60 LOGW(WHAT("{}, read len error", tag), REASON("len {}, need {}", len, sizeof(struct inotify_event)),
61 WILL("retry after 10 ms"));
62 std::this_thread::sleep_for(std::chrono::milliseconds(10));
63 continue;
64 }
65
66 int index = 0;
67 bool isChanged = false;
68 // 这里怕读到多个事件,使用while处理
69 do {
70 auto event = reinterpret_cast<struct inotify_event *>(buf + index);
71 // index向后移动evnet和name的长度
72 index += sizeof(struct inotify_event) + event->len;
73 LOGD(WHAT("event wd {}, mask {}, cookie {}, len {}, name {}", event->wd, event->mask, event->cookie,
74 event->len, event->name));
75 {
76 std::lock_guard<std::mutex> lock(m_fdMutex);
77 if (event->wd != m_watchFD) {
78 continue;
79 }
80 }
81 // 老的句柄被移除后,这里read会有一次ignore
82 if (event->mask & (IN_IGNORED)) {
83 continue;
84 }
85
86 // 到这里说明文件存在改动
87 isChanged = true;
88
89 // 这个文件被删除或重命名,需要重新进行添加
90 if (event->mask & (IN_DELETE_SELF | IN_MOVE_SELF)) {
91 LOGI(WHAT("{}, file {} was renamed or deleted, retry open", tag, m_filePath));
92 if (!addFileWatch()) {
93 LOGI(WHAT("{}, stop monitor", tag));
94 return;
95 }
96 }
97
98 } while (index < len);
99
100 if (isChanged) {
101 // 文件发生变化
102 LOGI(WHAT("{}, file change", tag));
103 notifyChangeCallback();
104 }
105 }
106
107 LOGI(WHAT("{} end", tag));
108 {
109 std::lock_guard<std::mutex> lock(m_fdMutex);
110 if (m_monitorFD >= 0) {
111 if (m_watchFD >= 0) {
112 inotify_rm_watch(m_monitorFD, m_watchFD);
113 m_watchFD = -1;
114 }
115 close(m_monitorFD);
116 m_monitorFD = -1;
117 }
118 }
119 }));
120 p.get_future().wait();
121 }
122
123 void stop() {
124 if (m_monitorFuture == nullptr || !m_monitorFuture->valid()) {
125 return;
126 }
127 do {
128 // 这里将总的fd关闭,将线程从read阻塞释放出来,然后将watch移除
129 std::lock_guard<std::mutex> lock(m_fdMutex);
130 if (m_monitorFD >= 0) {
131 if (m_watchFD >= 0) {
132 inotify_rm_watch(m_monitorFD, m_watchFD);
133 m_watchFD = -1;
134 }
135 close(m_monitorFD);
136 m_monitorFD = -1;
137 }
138 // 可能还没打开就关闭了,这里确保一下future是正常的,防止死锁
139 } while (m_monitorFuture->wait_for(std::chrono::milliseconds(1)) != std::future_status::ready);
140 m_monitorFuture = nullptr;
141 }
142
143 /**
144 * @brief 是否正在监控
145 * @return
146 */
147 bool isRunning() { return m_monitorFuture != nullptr; }
148
149 /**
150 * @brief 添加文件变化回调
151 * @param callback
152 */
153 void registerChangeCallback(const std::function<void()> &callback) {
154 std::lock_guard<std::mutex> lock(m_callbackMutex);
155 m_callbacks.emplace_back(callback);
156 }
157
158 /**
159 * @brief 移除文件变化回调
160 * @param callback
161 */
162 void unregisterChangeCallback(std::function<void()> callback) {
163 std::lock_guard<std::mutex> lock(m_callbackMutex);
164 for (auto it = m_callbacks.begin(); it != m_callbacks.end(); ++it) {
165 if (it->target<void *>() == callback.target<void *>()) {
166 m_callbacks.erase(it);
167 break;
168 }
169 }
170 }
171
172private:
173 std::string m_filePath;
174 std::shared_ptr<std::future<void>> m_monitorFuture;
175 std::mutex m_fdMutex;
176 int m_monitorFD = -1; // inotify的句柄
177 int m_watchFD = -1; // 被添加的文件句柄
178 std::mutex m_callbackMutex; // 对回调函数加锁
179 std::vector<std::function<void()>> m_callbacks; // 回调函数
180
181 /**
182 * @brief 通知变更的观察者,此文件变更
183 */
184 void notifyChangeCallback() {
185 std::vector<std::function<void()>> callbacks;
186 {
187 std::lock_guard<std::mutex> lock(m_callbackMutex);
188 callbacks = m_callbacks;
189 }
190 for (auto &callback : callbacks) {
191 callback();
192 }
193 }
194
195 /**
196 * @brief 添加一个文件到列表中
197 *
198 * @return 添加成功还是失败
199 */
200 bool addFileWatch() {
201 std::string tag = "add file(" + m_filePath + ") to watch";
202 {
203 std::lock_guard<std::mutex> lock(m_fdMutex);
204 if (m_monitorFD < 0) {
205 return false;
206 }
207 // 关闭上一次的句柄
208 if (m_watchFD >= 0) {
209 LOGI(WHAT("{}, remove watch fd {}", tag, m_watchFD));
210 inotify_rm_watch(m_monitorFD, m_watchFD);
211 m_watchFD = -1;
212 }
213 }
214 uint32_t failedCount = 0;
215 // 最大重试32位最大值的次数
216 while (++failedCount) {
217 // 重新加锁判断是否monitor已经被关了
218 {
219 std::lock_guard<std::mutex> lock(m_fdMutex);
220 if (m_monitorFD < 0) {
221 // 这里说明外部关闭了监听fd,也就是析构了,退出就好
222 return false;
223 }
224 // 监听所有事件,除了访问,打开和关闭,主要监听修改删除,添加成功直接返回
225 m_watchFD = inotify_add_watch(m_monitorFD, m_filePath.c_str(),
226 IN_ALL_EVENTS ^ (IN_ACCESS | IN_OPEN | IN_CLOSE));
227 if (m_watchFD > 0) {
228 LOGI(WHAT("{}, add watch success, fd {}", tag, m_watchFD));
229 return true;
230 }
231 }
232 if ((failedCount % 100) == 1) {
233 LOGW(WHAT("{}, inotify_add_watch failed", tag),
234 REASON("failed {} times, ec: {}", failedCount,
235 std::to_string(std::error_code(errno, std::system_category()))),
236 WILL("retry"));
237 }
238 std::this_thread::sleep_for(std::chrono::milliseconds(10));
239 }
240 LOGE(WHAT("{} failed", tag),
241 REASON("reach max failed count {}, ec: {}", failedCount - 1, m_filePath,
242 std::to_string(std::error_code(errno, std::system_category()))),
243 WILL("stop watch file"));
244 return false;
245 }
246};
二、源码分析
#
1. inotify_init
#
1// fs/notify/inotify/inotify_user.c
2SYSCALL_DEFINE1(inotify_init1, int, flags)
3{
4 return do_inotify_init(flags);
5}
6
7SYSCALL_DEFINE0(inotify_init)
8{
9 return do_inotify_init(0);
10}
do_inotify_init
就是创建了一个fd,提供了一系列操作inotify_fops
1// fs/notify/inotify/inotify_user.c
2/* inotify syscalls */
3static int do_inotify_init(int flags)
4{
5 struct fsnotify_group *group;
6 int ret;
7
8 /* Check the IN_* constants for consistency. */
9 BUILD_BUG_ON(IN_CLOEXEC != O_CLOEXEC);
10 BUILD_BUG_ON(IN_NONBLOCK != O_NONBLOCK);
11
12 if (flags & ~(IN_CLOEXEC | IN_NONBLOCK))
13 return -EINVAL;
14
15 /* fsnotify_obtain_group took a reference to group, we put this when we kill the file in the end */
16 group = inotify_new_group(inotify_max_queued_events);
17 if (IS_ERR(group))
18 return PTR_ERR(group);
19
20 ret = anon_inode_getfd("inotify", &inotify_fops, group,
21 O_RDONLY | flags);
22 if (ret < 0)
23 fsnotify_destroy_group(group);
24
25 return ret;
26}
1static const struct file_operations inotify_fops = {
2 .show_fdinfo = inotify_show_fdinfo,
3 .poll = inotify_poll,
4 .read = inotify_read,
5 .fasync = fsnotify_fasync,
6 .release = inotify_release,
7 .unlocked_ioctl = inotify_ioctl,
8 .compat_ioctl = inotify_ioctl,
9 .llseek = noop_llseek,
10};
2. read调用发生了什么
#
- read出来是
inotify_event
结构体,最后的name不确定长度
1/*
2 * struct inotify_event - structure read from the inotify device for each event
3 *
4 * When you are watching a directory, you will receive the filename for events
5 * such as IN_CREATE, IN_DELETE, IN_OPEN, IN_CLOSE, ..., relative to the wd.
6 */
7struct inotify_event {
8 __s32 wd; /* watch descriptor */
9 __u32 mask; /* watch mask */
10 __u32 cookie; /* cookie to synchronize two events */
11 __u32 len; /* length (including nulls) of name */
12 char name[0]; /* stub for possible name */
13};
inotify_read
的处理就是外面调用read时调用的函数
1static ssize_t inotify_read(struct file *file, char __user *buf,
2 size_t count, loff_t *pos)
3{
4 struct fsnotify_group *group;
5 struct fsnotify_event *kevent;
6 char __user *start;
7 int ret;
8 DEFINE_WAIT_FUNC(wait, woken_wake_function);
9
10 start = buf;
11 group = file->private_data;
12
13 add_wait_queue(&group->notification_waitq, &wait);
14 while (1) {
15 spin_lock(&group->notification_lock);
16 // 从group中取一个event,count代表size
17 kevent = get_one_event(group, count);
18 spin_unlock(&group->notification_lock);
19
20 pr_debug("%s: group=%p kevent=%p\n", __func__, group, kevent);
21
22 // 存在event,拷贝到用户态内存中,然后销毁内核态event,count-=size
23 if (kevent) {
24 ret = PTR_ERR(kevent);
25 if (IS_ERR(kevent))
26 break;
27 ret = copy_event_to_user(group, kevent, buf);
28 fsnotify_destroy_event(group, kevent);
29 if (ret < 0)
30 break;
31 buf += ret;
32 count -= ret;
33 continue;
34 }
35
36 ret = -EAGAIN;
37 if (file->f_flags & O_NONBLOCK)
38 break;
39 ret = -ERESTARTSYS;
40 if (signal_pending(current))
41 break;
42
43 if (start != buf)
44 break;
45
46 wait_woken(&wait, TASK_INTERRUPTIBLE, MAX_SCHEDULE_TIMEOUT);
47 }
48 remove_wait_queue(&group->notification_waitq, &wait);
49
50 if (start != buf && ret != -EFAULT)
51 ret = buf - start;
52 return ret;
53}
get_one_event
可以看到整体size包含inotify_event
的size和name的长度
- 如果count满足整体size,就删除队列第一个并返回
1/*
2 * Get an inotify_kernel_event if one exists and is small
3 * enough to fit in "count". Return an error pointer if
4 * not large enough.
5 *
6 * Called with the group->notification_lock held.
7 */
8static struct fsnotify_event *get_one_event(struct fsnotify_group *group,
9 size_t count)
10{
11 size_t event_size = sizeof(struct inotify_event);
12 struct fsnotify_event *event;
13
14 // 从group中取一个event,不删除list中的数据
15 event = fsnotify_peek_first_event(group);
16 if (!event)
17 return NULL;
18
19 pr_debug("%s: group=%p event=%p\n", __func__, group, event);
20
21 event_size += round_event_name_len(event);
22 if (event_size > count)
23 return ERR_PTR(-EINVAL);
24
25 /* held the notification_lock the whole time, so this is the
26 * same event we peeked above */
27 fsnotify_remove_first_event(group);
28
29 return event;
30}
fsnotify_peek_first_event
,group中存有通知的链表,这里取第一个
1/*
2 * Return the first event on the notification list without removing it.
3 * Returns NULL if the list is empty.
4 */
5struct fsnotify_event *fsnotify_peek_first_event(struct fsnotify_group *group)
6{
7 assert_spin_locked(&group->notification_lock);
8
9 if (fsnotify_notify_queue_is_empty(group))
10 return NULL;
11
12 return list_first_entry(&group->notification_list,
13 struct fsnotify_event, list);
14}