nhmk/示例/5-阻塞进程和线程/sleep.c

192 lines
6.8 KiB
C
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/*
* sleep.c - 创建一个 /proc 文件, 如果多个进程同时尝试打开该文件, 则除一个进程外, 其他进程都进入休眠状态
*/
#include <linux/atomic.h>
#include <linux/fs.h>
#include <linux/kernel.h> /* 引入 sprintf() */
#include <linux/module.h>
#include <linux/printk.h>
#include <linux/proc_fs.h>
#include <linux/types.h>
#include <linux/uaccess.h> /* 引入 get_user 和 put_user */
#include <linux/version.h>
#include <linux/wait.h> /* 用于让程序休眠和唤醒 */
#include <asm/current.h>
#include <asm/errno.h>
#if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 6, 0)
#define HAVE_PROC_OPS
#endif
/* 在这里我们保存最后接收到的消息, 以证明我们可以处理输入 */
#define MESSAGE_LENGTH 80
static char message[MESSAGE_LENGTH];
static struct proc_dir_entry *our_proc_file;
#define PROC_ENTRY_FILENAME "sleep"
/* 从文件"读取": 由于使用的是 file_operations (内核中定义文件操作的结构体),
* 其中不能使用procfs函数, 因此需要使用标准的文件操作函数(sprintf)来实现输出
*/
static ssize_t module_output(struct file *file, /* 参见 include/linux/fs.h */
char __user *buf, /* 将数据放入的缓冲区(在用户段) */
size_t len, /* 缓冲区的长度 */
loff_t *offset)
{
static int finished = 0;
int i;
char output_msg[MESSAGE_LENGTH + 30];
/* 返回 0 表示文件结束 */
if (finished) {
finished = 0;
return 0;
}
sprintf(output_msg, "最后输入:%s\n", message);
for (i = 0; i < len && output_msg[i]; i++)
put_user(output_msg[i], buf + i);
finished = 1;
return i; /* 返回"读取"的字节数 */
}
/* 当用户向 /proc 文件写入时,此函数接收输入 */
static ssize_t module_input(struct file *file, /* 文件本身 */
const char __user *buf, /* 输入缓冲区 */
size_t length, /* 缓冲区的长度 */
loff_t *offset) /* 文件的偏移 - 忽略 */
{
int i;
/* 将输入放入 Message 中,以便 module_output 可以在后面使用它 */
for (i = 0; i < MESSAGE_LENGTH - 1 && i < length; i++)
get_user(message[i], buf + i);
/* 标准的、以零终止的字符串 */
message[i] = '\0';
/* 返回使用的输入字符数 */
return i;
}
/* 如果文件当前被某人打开,则为 1 */
static atomic_t already_open = ATOMIC_INIT(0);
/* 等待我们文件的进程队列 */
static DECLARE_WAIT_QUEUE_HEAD(waitq);
/* 当 /proc 文件被打开时调用 */
static int module_open(struct inode *inode, struct file *file)
{
/* 尝试在不阻塞的情况下获取 */
if (!atomic_cmpxchg(&already_open, 0, 1)) {
/* 在不阻塞的情况下成功,允许访问 */
try_module_get(THIS_MODULE);
return 0;
}
/* 如果文件标志包含 O_NONBLOCK意味着进程不希望等待文件。在这种情况下因为文件已经被打开
* 我们应该返回 -EAGAIN表示“你必须再试一次”而不是阻塞一个更愿意保持清醒的进程。
*/
if (file->f_flags & O_NONBLOCK)
return -EAGAIN;
/* 这是调用 try_module_get(THIS_MODULE) 的正确位置,因为如果进程在模块内部的循环中,内核模块不能被移除 */
try_module_get(THIS_MODULE);
while (atomic_cmpxchg(&already_open, 0, 1)) {
int i, is_sig = 0;
pr_info("阻塞等待中\n");
/* 这个函数会将当前进程(包括任何系统调用)挂起, 并在函数调用wake_up(&waitq)后恢复
* 向进程发送退出信号也可以使其恢复信号(例如 ctrl-c)。
*/
wait_event_interruptible(waitq, !atomic_read(&already_open));
/* 判断当前进程是否有待处理的非阻塞信号(主要用于在等待途中随时可以被中断) */
for (i = 0; i < _NSIG_WORDS && !is_sig; i++)
is_sig = current->pending.signal.sig[i] & ~current->blocked.sig[i];
if (is_sig) {
/* 对于那些被中断的打开操作,不会自动关闭。不在这里减少计数器,后续程序就无法按照预期将计数器降到零,
* 这将导致我们有一个永不消失的模块,只能通过重启机器来清除。
*/
pr_info("已被中断, 退出完成\n");
module_put(THIS_MODULE);
return -EINTR;
}
}
return 0; /* 允许访问 */
}
/* 当 /proc 文件被关闭时调用 */
static int module_close(struct inode *inode, struct file *file)
{
/* 将 already_open 设置为零,以便 waitq 中的一个进程可以将 already_open 设置回1并打开文件。
* 所有进程将在 already_open 变回1时被唤醒所以它们将重新进入睡眠状态
*/
atomic_set(&already_open, 0);
/* 唤醒 waitq 中的所有进程,如果有人在等待文件,他们可以获得它 */
wake_up(&waitq);
module_put(THIS_MODULE);
return 0; /* 成功 */
}
/* 注册为 /proc 文件的结构,包含指向所有相关函数的指针 */
/* 我们的 proc 文件的文件操作。这是我们放置所有处理对文件进行操作
* 时调用的函数指针的地方。NULL表示我们不想处理某些操作。
*/
#ifdef HAVE_PROC_OPS
static const struct proc_ops file_ops_4_our_proc_file = {
.proc_read = module_output, /* 从文件"读取" */
.proc_write = module_input, /* 向文件"写入" */
.proc_open = module_open, /* 当 /proc 文件被打开时调用 */
.proc_release = module_close, /* 当文件关闭时调用 */
.proc_lseek = noop_llseek, /* 返回 file->f_pos */
};
#else
static const struct file_operations file_ops_4_our_proc_file = {
.read = module_output,
.write = module_input,
.open = module_open,
.release = module_close,
.llseek = noop_llseek,
};
#endif
/* 初始化模块 - 注册 proc 文件 */
static int __init sleep_init(void)
{
our_proc_file =
proc_create(PROC_ENTRY_FILENAME, 0644, NULL, &file_ops_4_our_proc_file);
if (our_proc_file == NULL) {
pr_debug("错误: 无法初始化 /proc/%s\n", PROC_ENTRY_FILENAME);
return -ENOMEM;
}
proc_set_size(our_proc_file, 80);
proc_set_user(our_proc_file, GLOBAL_ROOT_UID, GLOBAL_ROOT_GID);
pr_info("/proc/%s 已创建\n", PROC_ENTRY_FILENAME);
return 0;
}
/* 清理 - 从 /proc 注销我们的文件。如果在 waitq 中仍有进程等待,
* 这可能会变得危险,因为它们在我们的打开函数中,而这个函数将被卸载。
* 我会在第十章中解释如何避免在这种情况下移除内核模块。
*/
static void __exit sleep_exit(void)
{
remove_proc_entry(PROC_ENTRY_FILENAME, NULL);
pr_debug("/proc/%s 已移除\n", PROC_ENTRY_FILENAME);
}
module_init(sleep_init);
module_exit(sleep_exit);
MODULE_LICENSE("GPL");