Inotify

一、介绍 #

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 #

  • 都是调用do_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}
  • inotify_ops中重点关注read方法
 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}