0%

使用hrtimer的内核定时器

copy_from_usercopy_to_user

  • 这两个函数是内核程序和用户程序互相传递信息的函数
  • 原型是
    unsigned long copy_to_user(void __user *to, const void *from, unsigned long n)
    unsigned long copy_from_user(void *to, const void __user *from, unsigned long n)
  • 函数中的__user指的是这个指针指向的是用户空间的地址,也就是在用户空间使用&取地址得到的指针
  • 返回值是没有成功拷贝的字节数,0代表成功
  • 这两个函数不能在中断上下文中使用,因为这两个函数可能导致休眠和阻塞,影响进程切换

    刚拿到一个用户地址的时候可以读写,但是过一段时间就不能了

  • 可能原因
    • 用户空间地址可能已经发生了变化。用户进程可能已经释放了该地址或重新分配了内存,导致原来的地址不再有效
    • 操作系统的内存保护机制可能导致内核无法访问用户空间的地址
    • 如果内核和用户空间的数据在进行同步操作时出现问题,可能导致地址的有效性检测失败。例如,内核线程和用户线程在不同步的情况下对同一地址进行操作,可能会引发竞争条件。

锁定用户页面

  • 使用get_user_pages函数
    long get_user_pages(unsigned long start, unsigned long nr_pages,unsigned int gup_flags, struct page **pages);
  • 传递参数pages是供函数获取到页之后修改用的,相当于返回值
  • 然后使用kmap_local_page获取到内核空间的页地址
    static inline void *kmap_local_page(struct page *page);
  • 通过内核地址向用户空间写入内容
  • 因为已经获取到了用户地址映射到内核空间的地址,因此可以直接写入
  • 使用memcpy:
    memcpy(<内核页地址>+((unsigned long)<用户给的用户空间地址> & ~PAGE_MASK), &<要拷贝的源地址>, sizeof(value));
  • 用户空间地址与页掩码取反的结果求与,实际上是求出用户空间地址在当前页面下的偏移量,加上页地址得到完整的内核空间地址

    使用hrtimer的定时器

  • hrtimer 是 Linux 内核中的高分辨率定时器(High-Resolution Timer)子系统的一部分,它允许开发人员精确地调度和管理定时任务。与传统的 jiffies 计时机制不同,hrtimer 提供了纳秒级的精度,这对于需要精确时间管理的应用程序非常有用,例如实时系统、精确定时事件的触发等。
  • 使用回调函数的方式处理定时器到达的问题,注意回调函数是在中断语境的,要防止阻塞。
  • 代码演示
    #include <linux/module.h>
    #include <linux/hrtimer.h>
    #include <linux/ktime.h>
    #include <linux/sched.h>
    #include <linux/signal.h>
    #include <linux/fs.h>
    #include <linux/cdev.h>
    #include <linux/device.h>
    #include <linux/slab.h>
    #include <linux/uaccess.h>

    #define DEVICE_NAME "my_hrtimer_device"
    #define CLASS_NAME "my_hrtimer_class"
    #define IOCTL_SET_PID _IOW('f', 1, pid_t*)

    static struct hrtimer my_hrtimer;
    static ktime_t kt_periode;
    static struct class* my_hrtimer_class = NULL;
    static struct device* my_hrtimer_device = NULL;
    static dev_t dev_num;
    static struct cdev my_cdev;
    static pid_t user_pid = -1;
    static struct task_struct *user_task = NULL;

    enum hrtimer_restart my_hrtimer_callback(struct hrtimer *timer) {
    if (user_task) {
    send_sig_info(SIGUSR1, SEND_SIG_PRIV, user_task); // 发送信号给用户进程
    }

    hrtimer_forward_now(timer, kt_periode);
    return HRTIMER_RESTART;
    }

    static long my_ioctl(struct file *file, unsigned int cmd, unsigned long arg) {
    switch (cmd) {
    case IOCTL_SET_PID:
    if (copy_from_user(&user_pid, (pid_t __user *)arg, sizeof(user_pid))) {
    return -EFAULT;
    }
    user_task = pid_task(find_vpid(user_pid), PIDTYPE_PID);
    if (!user_task) {
    return -ESRCH; // 无法找到该 PID 对应的进程
    }
    printk(KERN_INFO "Received PID: %d\n", user_pid);
    break;
    default:
    return -ENOTTY;
    }
    return 0;
    }

    static struct file_operations fops = {
    .unlocked_ioctl = my_ioctl,
    };

    static int __init my_hrtimer_init(void) {
    int ret;

    printk(KERN_INFO "Initializing hrtimer module.\n");

    // 注册字符设备
    ret = alloc_chrdev_region(&dev_num, 0, 1, DEVICE_NAME);
    if (ret < 0) {
    printk(KERN_ERR "Failed to allocate char device region\n");
    return ret;
    }

    cdev_init(&my_cdev, &fops);
    ret = cdev_add(&my_cdev, dev_num, 1);
    if (ret < 0) {
    unregister_chrdev_region(dev_num, 1);
    printk(KERN_ERR "Failed to add char device\n");
    return ret;
    }

    my_hrtimer_class = class_create(CLASS_NAME);
    if (IS_ERR(my_hrtimer_class)) {
    cdev_del(&my_cdev);
    unregister_chrdev_region(dev_num, 1);
    printk(KERN_ERR "Failed to create class\n");
    return PTR_ERR(my_hrtimer_class);
    }

    my_hrtimer_device = device_create(my_hrtimer_class, NULL, dev_num, NULL, DEVICE_NAME);
    if (IS_ERR(my_hrtimer_device)) {
    class_destroy(my_hrtimer_class);
    cdev_del(&my_cdev);
    unregister_chrdev_region(dev_num, 1);
    printk(KERN_ERR "Failed to create device\n");
    return PTR_ERR(my_hrtimer_device);
    }

    // 设置定时器周期
    kt_periode = ktime_set(0, 1000000000); // 1秒

    // 初始化定时器
    hrtimer_init(&my_hrtimer, CLOCK_MONOTONIC, HRTIMER_MODE_REL);
    my_hrtimer.function = my_hrtimer_callback;

    // 启动定时器
    hrtimer_start(&my_hrtimer, kt_periode, HRTIMER_MODE_REL);

    return 0;
    }

    static void __exit my_hrtimer_exit(void) {
    int ret;

    // 取消定时器
    ret = hrtimer_cancel(&my_hrtimer);
    if (ret) {
    printk(KERN_INFO "Timer was still in use.\n");
    }

    // 注销设备
    device_destroy(my_hrtimer_class, dev_num);
    class_destroy(my_hrtimer_class);
    cdev_del(&my_cdev);
    unregister_chrdev_region(dev_num, 1);

    printk(KERN_INFO "Exiting hrtimer module.\n");
    }

    module_init(my_hrtimer_init);
    module_exit(my_hrtimer_exit);

    MODULE_LICENSE("GPL");
    MODULE_AUTHOR("Frank");
    MODULE_DESCRIPTION("A simple hrtimer example module with signal to wake up user process");
  • 用户端程序(执行需要sudo权限)
    #include <signal.h>
    #include <stdio.h>
    #include <stdlib.h>
    #include <unistd.h>
    #include <fcntl.h>
    #include <sys/ioctl.h>
    #include <sys/time.h>

    #define IOCTL_SET_PID _IOW('f', 1, pid_t*)

    // 返回的是微秒时间戳
    __uint64_t timestamp()
    {
    struct timeval tv;
    gettimeofday(&tv, NULL);
    return (tv.tv_sec * 1000000 + tv.tv_usec);
    }
    __uint64_t prev;

    void handle_signal(int sig) {
    if (sig == SIGUSR1) {
    printf("Received SIGUSR1 signal!time interval is %lu\n", timestamp() - prev);
    prev = timestamp();
    }
    }

    int main() {
    prev = timestamp();
    // 设置信号处理程序
    struct sigaction sa;
    sa.sa_handler = handle_signal;
    sa.sa_flags = 0;
    sigemptyset(&sa.sa_mask);

    if (sigaction(SIGUSR1, &sa, NULL) == -1) {
    perror("sigaction");
    exit(EXIT_FAILURE);
    }

    int fd = open("/dev/my_hrtimer_device", O_RDWR);
    if (fd == -1) {
    perror("open");
    exit(EXIT_FAILURE);
    }

    pid_t pid = getpid();
    if (ioctl(fd, IOCTL_SET_PID, &pid) == -1) {
    perror("ioctl");
    close(fd);
    exit(EXIT_FAILURE);
    }

    // 等待信号
    while (1) {
    pause(); // 等待信号
    }

    close(fd);
    return 0;
    }
  • 结果
    text
    Received SIGUSR1 signal!time interval is 999464
    Received SIGUSR1 signal!time interval is 1000214
    Received SIGUSR1 signal!time interval is 999859
    Received SIGUSR1 signal!time interval is 1000258
    Received SIGUSR1 signal!time interval is 999416
    Received SIGUSR1 signal!time interval is 1000597
    Received SIGUSR1 signal!time interval is 999601
    Received SIGUSR1 signal!time interval is 1000106
    Received SIGUSR1 signal!time interval is 999795
    Received SIGUSR1 signal!time interval is 999836
    Received SIGUSR1 signal!time interval is 999758
    Received SIGUSR1 signal!time interval is 1000267
    Received SIGUSR1 signal!time interval is 999734
    Received SIGUSR1 signal!time interval is 1000015
    Received SIGUSR1 signal!time interval is 100045

调度问题

  • 如果在hrtimer的回调函数中发送信号到用户态,此时往往达不到超过内核进程调度tick的精确度
  • 因此建议直接在hrtimer的回调函数中直接执行需要完成的工作
  • hrtimer的回调函数不能执行schedule()函数,因为这个函数所在的语境一般是中断返回函数,是不允许进程切换的,是关闭中断的