192 lines
6.8 KiB
C
192 lines
6.8 KiB
C
/*
|
||
* 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");
|
||
|