Linux 驱动之高级字符设备

这篇具有很好参考价值的文章主要介绍了Linux 驱动之高级字符设备。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

IO模型详解

什么是IO呢?

IO的英文全称是input和output,翻译过来就是输入和输出。

在冯.诺依曼结构中,将计算机分成分为5个部分:运算器、控制器、存储器、输入设备、输出设备。其中输入设备指的是向计算机输入数据或者信息,如鼠标,键盘都是输入设备。输出设备指的是用于接收计算机输出信息的设备,如电脑显示器。

比如我们在键盘上敲击一个按键,然后这个按键的数据会传递给计算机。计算机经过运算以后,把数据显示在显示器上。

Linux 驱动之高级字符设备

IO执行过程

操作系统(Linux)负责对计算机的资源进行管理,应用程序运行在操作系统上,处在用户空间。

应用程序不能直接对硬件进行操作,只能通过操作系统提供的API来操作硬件。

所以当我们在进行IO操作的时候,如读写磁盘的操作,进程需要切换到内核空间才可以执行这样的操作。并且应用程序不能直接操作内核空间的数据,需要把内核空间的数据拷贝到用户空间。

一个完整的IO过程包含以下几个步骤:

  1. 应用程序向操作系统发起IO调用请求(系统调用)。
  2. 操作系统准备数据,把IO设备的数据加载到内核缓冲区。
  3. 操作系统拷贝数据,把内核缓冲区的数据从内核空间拷贝到应用空间。

IO模型引入

但是在IO执行过程中,由于CPU和内存的速度远远高于外设的速度,所以就存在速度严重不匹配的情况。举个例子,比如我要给磁盘写入1000M的数据,CPU输出1000M的数据可能只需要几秒钟,但是磁盘如果要接收1000M的数据可能需要几分钟。

怎么办呢?就可以使用IO模型进行编程。

IO模型的种类

IO模型有阻塞IO,非阻塞IO,信号驱动IO,IO多路复用,异步IO。其中前四个被称之为同步IO

同步IO:需要等待

异步IO:不需要等待

阻塞IO

Linux 驱动之高级字符设备

通俗说明

A拿着一支鱼竿在河边钓鱼,并且一直在鱼竿前等,在等的时候不做其他的事情,十分专心。只有鱼上钩的时,才结束掉等的动作,把鱼钓上来。

优点:即时获取数据,缺点:效率不高

等待队列
什么是等待队列

等待队列是内核实现阻塞和唤醒的内核机制。等待队列以循环链表为基础结构,链表头和链表项分别为等待队列头和等待队列元素。整个等待队列由等待队列头进行管理。
等待队列头使用结构体 wait_queue_head_t来表示,等待队列头就是一个等待队列的头部,这个结构体定义在文件include/linux/wait.h里面,结构体内容如下:

struct wait_queue_head {
    spinlock_t        lock; 用于互斥访问的自旋锁
    struct list_head    head;  //链表头
};
typedef struct wait_queue_head wait_queue_head_t;

结构体wait_queue_entry表示等待队列项,结构体内容如下:

struct wait_queue_entry {
    unsigned int        flags;
    void            *private;  //指向等待队列的进程task_struct
    wait_queue_func_t    func;  //唤醒函数
    struct list_head    entry; //链表元素,将wait_queue_entry 挂到wait_queue_head_t
};

Linux 驱动之高级字符设备

定义并初始化等待队列头

方法一

  1. 定义一个等待队列头

    wait_queue_head_t test_wq;//定义一个等待队列的头
    
  2. 初始化等待队列头

    可以使用init_waitqueue_head函数初始化等待队列头,函数原型如下:

    extern void __init_waitqueue_head(struct wait_queue_head *wq_head, const char *name, struct lock_class_key *);
    
    #define init_waitqueue_head(wq_head)                        \
        do {                                    \
            static struct lock_class_key __key;                \
                                            \
            __init_waitqueue_head((wq_head), #wq_head, &__key);        \
        } while (0)
    

方法二

使用宏DECLARE_WAIT_QUEUE_HEAD来一次性完成等待队列头的定义和初始化。原型:

#define DECLARE_WAIT_QUEUE_HEAD(name) \
    struct wait_queue_head name = __WAIT_QUEUE_HEAD_INITIALIZER(name)
创建等待队列项

创建等待队列项一般使用宏DECLARE_WAITQUEUE(name, tsk)。原型:

#define DECLARE_WAITQUEUE(name, tsk)                        \
    struct wait_queue_entry name = __WAITQUEUE_INITIALIZER(name, tsk)

参数:

  • name:就是等待队列项的名字。
  • task:表示这个等待队列项属于哪个任务(进程),一般设置为current。在Linux内核中current相当于一个全局变量,表示当前进程。因此DECLARE_WAITQUEUE就是给当前正在运行的进程创建并初始化了一个等待队列项。

举例:

DECLARE_WAITQUEUE(wait,current); //给当前正在运行的进行创建一个名为wait的等待队列项
add_wait_queue(wq,&wait);        //将wait这个等待队列项加到wq这个等待队列当中
添加/删除队列

当设备不可访问的时候就需要将进程对应的等待队列项添加到前面创建的等待队列头中,只有添加到等待队列头中以后进程才能进入休眠态。当设备可以访问以后再将进程对应的等待队列项从等待队列头中移除即可。

等待队列项添加队列函数,原型如下所示:

extern void add_wait_queue(struct wait_queue_head *wq_head, struct wait_queue_entry *wq_entry);

参数:

  • wq_head:等待队列项要加入的等待队列头
  • wq_entry:要加入的等待队列项

等待队列项移除队列函数,原型如下所示:

extern void remove_wait_queue(struct wait_queue_head *wq_head, struct wait_queue_entry *wq_entry);

参数:

  • wq_head:要删除的等待队列项所处的等待队列头
  • wq_entry:要删除的等特队列项
等待事件
  • wait_event(wq, condition)
    功能:不可中断的阻塞等待,让调用进程进入不可中断的睡眠状态,在等待队列里面睡眠直到condition变成真,被内核唤醒。
    参数:
    • wqwait_queue_head_t类型变量
    • condition:等待条件。为假时才可以进入休眠。如果condition为真,则不会休眠。
  • wait_event_interruptible(wq, condition)函数
    功能:可中断的阻塞等待,让调用进程进入可中断的睡眠状态,直到 condition 变成真被内核唤醒或信号打断唤醒。
    参数:
    • wqwait_queue_head_t类型变量
    • condition:等待条件。为假时才可以进入休眠。如果condition为真,则不会休眠。
等待队列唤醒

当设备可以使用的时候就要唤醒进入休眠态的进程,唤醒可以使用如下两个函数:

//功能:唤醒所有休眠进程
wake_up(wait_queue_head_t *q)
//功能:唤醒可中断的休眠进程        
wake_up_interruptible(wait_queue_head_t *q)
等待队列的使用方法
  1. 初始化等待队列头,并将条件置成假(condition=0)
  2. 在需要阻塞的地方调用wait_event(),使进程进入休眠。
  3. 当条件满足时,需要解除休眠,先将条件置成真(condition=1),然后调用wake_up函数唤醒等待队列中的休眠进程。
例子

usr.c

#include <linux/module.h>
#include <linux/init.h>
#include <linux/moduleparam.h>
#include <linux/fs.h>
#include <linux/kdev_t.h>
#include <linux/cdev.h>
#include <linux/device.h>
#include <linux/uaccess.h>
#include <linux/errno.h>

#include <asm/atomic.h>
#include <linux/semaphore.h>

#include <linux/wait.h>
struct device_test
{
    dev_t dev_num;
    struct cdev cdev_test;

    int major;
    int minor;

    struct class *class;
    struct device *device;

    char kbuf[32];
    int flag;
};

struct device_test dev1;

DECLARE_WAIT_QUEUE_HEAD(read_wq);

static int cdev_test_open(struct inode *inode, struct file *file)
{

    file->private_data = &dev1;
    printk("This is a cdev_test_open\n");
    return 0;
}

static ssize_t cdev_test_read(struct file *file, char __user *buf, size_t size, loff_t *off)
{
    struct device_test *dev = (struct device_test *)file->private_data;

    wait_event_interruptible(read_wq, dev->flag);
    if (copy_to_user(buf, dev->kbuf, strlen(dev->kbuf)) != 0)
    {
        printk("copy_to_user is error");
        return -EFAULT;
    }
    return 0;
}

static ssize_t cdev_test_write(struct file *file, const char __user *buf, size_t size, loff_t *off)
{
    struct device_test *dev = (struct device_test *)file->private_data;
    if (copy_from_user(dev->kbuf, buf, size) != 0)
    {
        printk("copy_from_user is error");
        return -EFAULT;
    }
    dev->flag = 1;
    wake_up_interruptible(&read_wq);
    printk("kbuf is %s\n", dev->kbuf);
    return 0;
}

static int cdev_test_release(struct inode *inode, struct file *file)
{

    return 0;
}

struct file_operations cdev_test_ops = {
    .owner = THIS_MODULE,
    .open = cdev_test_open,
    .read = cdev_test_read,
    .write = cdev_test_write,
    .release = cdev_test_release,
};

static int usr_init(void)
{
    int ret;

    ret = alloc_chrdev_region(&dev1.dev_num, 0, 1, "alloc_name");
    if (ret < 0)
    {
        goto err_chrdev;
        printk("alloc_chrdev_region failed\n");
    }
    printk("alloc_chrdev_region succeed\n");

    dev1.major = MAJOR(dev1.dev_num);
    dev1.minor = MINOR(dev1.dev_num);
    printk("major is %d\n", dev1.major);
    printk("minor is %d\n", dev1.minor);

    dev1.cdev_test.owner = THIS_MODULE;
    cdev_init(&dev1.cdev_test, &cdev_test_ops);
    ret = cdev_add(&dev1.cdev_test, dev1.dev_num, 1);
    if (ret < 0)
    {
        goto err_chr_add;
    }

    dev1.class = class_create(THIS_MODULE, "test");
    if (IS_ERR(dev1.class))
    {
        goto err_class_create;
    }
    device_create(dev1.class, NULL, dev1.dev_num, NULL, "test");
    if (IS_ERR(dev1.device))
    {
        goto err_class_device;
    }

    dev1.flag = 0;

    return 0;
err_class_device:
    class_destroy(dev1.class);

err_class_create:
    cdev_del(&dev1.cdev_test);

err_chr_add:
    unregister_chrdev_region(dev1.dev_num, 1);

err_chrdev:
    return ret;
}

static void usr_exit(void)
{
    unregister_chrdev_region(dev1.dev_num, 1);
    cdev_del(&dev1.cdev_test);

    device_destroy(dev1.class, dev1.dev_num);
    class_destroy(dev1.class);
    printk("bye bye\n");
}

module_init(usr_init);
module_exit(usr_exit);

MODULE_LICENSE("GPL");
MODULE_AUTHOR("fengzc");
MODULE_VERSION("v1.0");

write.c

#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>

int main(int argc, char *argv[])
{
    int fd;
    char buf1[32] = {0};
    char buf2[32] = {"nihao"};
    fd = open("/dev/test", O_RDWR); // 打开设备节点
    if (fd < 0)
    {
        perror("open error \n");
        return fd;
    }
    printf("write before\n");
    write(fd, buf2, strlen(buf2));
    printf("write after\n");
    close(fd);
    return 0;
}

read.c

#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>

int main(int argc, char *argv[])
{
    int fd;
    char buf1[32] = {};
    char buf2[32] = {"nihao"};
    fd = open("/dev/test", O_RDWR); // 打开设备节点
    if (fd < 0)
    {
        perror("open error \n");
        return fd;
    }
    printf("read before\n");
    read(fd, buf1, 32);
    printf("buf is %s\n",buf1);
    printf("read after\n");
    close(fd);
    return 0;
}

输出:(两个终端执行)

  1. fengzc@ubuntu:~/study/drivers_test/16_wq$ sudo insmod usr.ko
    fengzc@ubuntu:~/study/drivers_test/16_wq$ sudo ./read.out
    read before

  2. fengzc@ubuntu:~/study/drivers_test/16_wq$ sudo ./write.out
    write before
    write after

  3. fengzc@ubuntu:~/study/drivers_test/16_wq$ sudo insmod usr.ko
    fengzc@ubuntu:~/study/drivers_test/16_wq$ sudo ./read.out
    read before
    buf is nihao
    read after

非阻塞IO

Linux 驱动之高级字符设备

通俗说明

B也在河边钓鱼,但是B不想将自己的所有时间都花费在钓鱼上,在等鱼上钩这个时间段中,B也在做其他的事情(一会看看书,一会读读报纸,一会又去看其他人的钓鱼等),但B在做这些事情的时候,每隔一个固定的时间检查鱼是否上钩。一旦检查到有鱼上钩,就停下手中的事情,把鱼钓上来。B在检查鱼竿是否有鱼,是一个轮询的过程。

优点:在这个等待过程,可以做其他事,缺点:轮询对于CPU来说是较大的浪费

通过应用程序选择阻塞和非阻塞

应用程序可以使用如下所示示例代码来实现阻塞访问:

fd = open("/dev/xxx_dev",O_RDWR);  // 阻塞方式打开
ret = read(fd, &data, sizeof(data));  //读取数据

可以看出对于设备驱动文件的默认读取方式就是阻塞式的。

如果应用程序要采用非阻塞的方式来访问驱动设备文件,可以使用如下所示代码:

fd = open("/dev/xxx_dev", O_RDWR | O_NONBLOCK); //非阻塞方式打开
ret = read(fd,&data,sizeof(data));             //读取数据

使用open函数打开“/dev/xxx_dev”设备文件的时候添加了参数“O_NONBLOCK”,表示以非阻塞方式打开设备,这样从设备中读取数据的时候就是非阻塞方式的了

注意:实现非阻塞需要应用去支持

例子

usr.c

#include <linux/module.h>
#include <linux/init.h>
#include <linux/moduleparam.h>
#include <linux/fs.h>
#include <linux/kdev_t.h>
#include <linux/cdev.h>
#include <linux/device.h>
#include <linux/uaccess.h>
#include <linux/errno.h>

#include <asm/atomic.h>
#include <linux/semaphore.h>

#include <linux/wait.h>
struct device_test
{
    dev_t dev_num;
    struct cdev cdev_test;

    int major;
    int minor;

    struct class *class;
    struct device *device;

    char kbuf[32];
    int flag;
};

struct device_test dev1;

DECLARE_WAIT_QUEUE_HEAD(read_wq);

static int cdev_test_open(struct inode *inode, struct file *file)
{

    file->private_data = &dev1;
    printk("This is a cdev_test_open\n");
    return 0;
}

static ssize_t cdev_test_read(struct file *file, char __user *buf, size_t size, loff_t *off)
{
    struct device_test *dev = (struct device_test *)file->private_data;

    if (file->f_flags & O_NONBLOCK)
    {
        if (dev->flag != 1)
        {
            return -EAGAIN;
        }
    }

    wait_event_interruptible(read_wq, dev->flag);
    if (copy_to_user(buf, dev->kbuf, strlen(dev->kbuf)) != 0)
    {
        printk("copy_to_user is error");
        return -EFAULT;
    }
    return 0;
}

static ssize_t cdev_test_write(struct file *file, const char __user *buf, size_t size, loff_t *off)
{
    struct device_test *dev = (struct device_test *)file->private_data;
    if (copy_from_user(dev->kbuf, buf, size) != 0)
    {
        printk("copy_from_user is error");
        return -EFAULT;
    }
    dev->flag = 1;
    wake_up_interruptible(&read_wq);
    printk("kbuf is %s\n", dev->kbuf);
    return 0;
}

static int cdev_test_release(struct inode *inode, struct file *file)
{

    return 0;
}

struct file_operations cdev_test_ops = {
    .owner = THIS_MODULE,
    .open = cdev_test_open,
    .read = cdev_test_read,
    .write = cdev_test_write,
    .release = cdev_test_release,
};

static int usr_init(void)
{
    int ret;

    ret = alloc_chrdev_region(&dev1.dev_num, 0, 1, "alloc_name");
    if (ret < 0)
    {
        goto err_chrdev;
        printk("alloc_chrdev_region failed\n");
    }
    printk("alloc_chrdev_region succeed\n");

    dev1.major = MAJOR(dev1.dev_num);
    dev1.minor = MINOR(dev1.dev_num);
    printk("major is %d\n", dev1.major);
    printk("minor is %d\n", dev1.minor);

    dev1.cdev_test.owner = THIS_MODULE;
    cdev_init(&dev1.cdev_test, &cdev_test_ops);
    ret = cdev_add(&dev1.cdev_test, dev1.dev_num, 1);
    if (ret < 0)
    {
        goto err_chr_add;
    }

    dev1.class = class_create(THIS_MODULE, "test");
    if (IS_ERR(dev1.class))
    {
        goto err_class_create;
    }
    device_create(dev1.class, NULL, dev1.dev_num, NULL, "test");
    if (IS_ERR(dev1.device))
    {
        goto err_class_device;
    }

    dev1.flag = 0;

    return 0;
err_class_device:
    class_destroy(dev1.class);

err_class_create:
    cdev_del(&dev1.cdev_test);

err_chr_add:
    unregister_chrdev_region(dev1.dev_num, 1);

err_chrdev:
    return ret;
}

static void usr_exit(void)
{
    unregister_chrdev_region(dev1.dev_num, 1);
    cdev_del(&dev1.cdev_test);

    device_destroy(dev1.class, dev1.dev_num);
    class_destroy(dev1.class);
    printk("bye bye\n");
}

module_init(usr_init);
module_exit(usr_exit);

MODULE_LICENSE("GPL");
MODULE_AUTHOR("fengzc");
MODULE_VERSION("v1.0");

read.c

#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>

int main(int argc, char *argv[])
{
    int fd;
    char buf1[32] = {0};
    char buf2[32] = {"nihao"};
    fd = open("/dev/test", O_RDWR | O_NONBLOCK); // 打开设备节点
    if (fd < 0)
    {
        perror("open error \n");
        return fd;
    }
    printf("read before\n");
    while (1)
    {
        read(fd, buf1, 32);
        printf("buf is %s\n", buf1);
        sleep(1);
    }

    printf("read after\n");
    close(fd);
    return 0;
}

write.c

#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>

int main(int argc, char *argv[])
{
    int fd;
    char buf1[32] = {0};
    char buf2[32] = {"nihao"};
    fd = open("/dev/test", O_RDWR| O_NONBLOCK); // 打开设备节点
    if (fd < 0)
    {
        perror("open error \n");
        return fd;
    }
    printf("write before\n");
    write(fd, buf2, strlen(buf2));
    printf("write after\n");
    close(fd);
    return 0;
}

输出:

Linux 驱动之高级字符设备

信号驱动IO

Linux 驱动之高级字符设备

通俗说明

C也在河边钓鱼,但与A、B不同的是,C比较聪明,他给鱼竿上挂一个铃铛,当有鱼上钩的时候,这个铃铛就会被碰响,C就会将鱼钓上来。

信号驱动IO模型,应用进程告诉内核:当数据报准备好的时候,给我发送一个信号,对SIGIO信号进行捕捉,并且调用我的信号处理函数来获取数据报。

概念

信号驱动IO不需要应用程序去查询设备的状态。一旦设备准备就绪,就触发SIGIO信号,该信号会通知应用程序数据已经到来。

信号驱动IO需要应用层与驱动层一起配合实现

应用程序使用信号驱动IO的步骤

步骤1:注册信号处理函数。应用程序使用signal()函数来注册SIGIO信号的信号处理函数。

步骤2:设置能够接收这个信号的进程。

步骤3:开启信号驱动IO,通常使用fcntlF_SETFL命令打开FASYNC标志

  • fcntl函数
    函数原型:int fcntl(int fd,int cmd,... /*arg */ )

    函数功能:fcntl函数可以用来操作文件描述符。

    函数参数:

    • fd:被操作的文件描述符。
    • cmd:操作文件描述符的命令,cmd参数决定了要如何操作文件描述符fd
    • ...:根据cmd的参数来决定是不是需要使用第三个参数。
    cmd 描述
    F_DUPFD 复制文件描述符
    F_GETFD 获取文件描述符标志
    F_SETFD 设置文件描述符标志
    F_GETFL 获取文件状态标志
    F_SETFL 设置文件状态标志
    F_GETLK 类似F_SETLK,但等待返回
    F_SETLK 设置文件锁
    F_SETLKW 类似F_SETLK,但等待返回
    F_GETOWN 获取当前接收SIGIO和SIGURG信号的进程ID和进程组ID
    F_SETOWN 设置当前接收SIGIO和SIGURG信号的进程ID和进程组ID
驱动程序实现fasync方法

步骤1:
当应用程序开启信号驱动IO时。会触发驱动中的fasync函数。所以首先在file_operations结构体中实现fasync函数。函数原型如下:

int (*fasync) (int fd,struct file *filp, int on)

步骤2:
在驱动中的fasync函数中调用fasync_helper函数来操作fasync_struct结构体,fasync_helper数原型如下:

int fasync_helper(int fd, struct file * filp, int on,struct fasync _struct **fapp)

步骤3:
当设备准备好的时候,驱动程序需要调用kill_fasync函数通知应用程序,此时应用程序的SIGIO信号处理函数就会被执行。kill_fasync负责发送指定的信号。函数原型如下:

void kill_fasync(struct fasync_struct **fp, int sig, int band)

函数参数:

  • fp:要操作的 fasync_struct
  • sig:发送的信号。
  • band:可读的时候设置成POLLIN,可写的时候设置成POLLOUT
例子

usr.c

#include <linux/module.h>
#include <linux/init.h>
#include <linux/moduleparam.h>
#include <linux/fs.h>
#include <linux/kdev_t.h>
#include <linux/cdev.h>
#include <linux/device.h>
#include <linux/uaccess.h>
#include <linux/errno.h>

#include <asm/atomic.h>
#include <linux/semaphore.h>

#include <linux/wait.h>
#include <linux/poll.h>

struct device_test
{
    dev_t dev_num;
    struct cdev cdev_test;

    int major;
    int minor;

    struct class *class;
    struct device *device;

    char kbuf[32];
    int flag;
    struct fasync_struct *fasync;
};

struct device_test dev1;

DECLARE_WAIT_QUEUE_HEAD(read_wq);

static int cdev_test_open(struct inode *inode, struct file *file)
{

    file->private_data = &dev1;
    printk("This is a cdev_test_open\n");
    return 0;
}

static ssize_t cdev_test_read(struct file *file, char __user *buf, size_t size, loff_t *off)
{
    struct device_test *dev = (struct device_test *)file->private_data;

    if (file->f_flags & O_NONBLOCK)
    {
        if (dev->flag != 1)
        {
            return -EAGAIN;
        }
    }

    wait_event_interruptible(read_wq, dev->flag);
    if (copy_to_user(buf, dev->kbuf, strlen(dev->kbuf)) != 0)
    {
        printk("copy_to_user is error");
        return -EFAULT;
    }
    return 0;
}

static ssize_t cdev_test_write(struct file *file, const char __user *buf, size_t size, loff_t *off)
{
    struct device_test *dev = (struct device_test *)file->private_data;
    if (copy_from_user(dev->kbuf, buf, size) != 0)
    {
        printk("copy_from_user is error");
        return -EFAULT;
    }
    dev->flag = 1;
    wake_up_interruptible(&read_wq);

    kill_fasync(&dev->fasync, SIGIO, POLLIN);

    printk("kbuf is %s\n", dev->kbuf);
    return 0;
}

static int cdev_test_release(struct inode *inode, struct file *file)
{
    return 0;
}

static __poll_t cdev_test_poll(struct file *file, struct poll_table_struct *p)
{
    struct device_test *dev = (struct device_test *)file->private_data;

    __poll_t mask = 0;

    poll_wait(file, &read_wq, p);

    if (dev->flag == 1)
    {
        mask |= POLLIN;
    }

    
    return mask;
}

static int cdev_test_fasync(int fd, struct file *file, int on)
{
    struct device_test *dev = (struct device_test *)file->private_data;
    return fasync_helper(fd, file, on, &dev->fasync);
}

struct file_operations cdev_test_ops = {
    .owner = THIS_MODULE,
    .open = cdev_test_open,
    .read = cdev_test_read,
    .write = cdev_test_write,
    .release = cdev_test_release,
    .poll = cdev_test_poll,
    .fasync = cdev_test_fasync,
};

static int usr_init(void)
{
    int ret;

    ret = alloc_chrdev_region(&dev1.dev_num, 0, 1, "alloc_name");
    if (ret < 0)
    {
        goto err_chrdev;
        printk("alloc_chrdev_region failed\n");
    }
    printk("alloc_chrdev_region succeed\n");

    dev1.major = MAJOR(dev1.dev_num);
    dev1.minor = MINOR(dev1.dev_num);
    printk("major is %d\n", dev1.major);
    printk("minor is %d\n", dev1.minor);

    dev1.cdev_test.owner = THIS_MODULE;
    cdev_init(&dev1.cdev_test, &cdev_test_ops);
    ret = cdev_add(&dev1.cdev_test, dev1.dev_num, 1);
    if (ret < 0)
    {
        goto err_chr_add;
    }

    dev1.class = class_create(THIS_MODULE, "test");
    if (IS_ERR(dev1.class))
    {
        goto err_class_create;
    }
    device_create(dev1.class, NULL, dev1.dev_num, NULL, "test");
    if (IS_ERR(dev1.device))
    {
        goto err_class_device;
    }

    dev1.flag = 0;
    return 0;
err_class_device:
    class_destroy(dev1.class);

err_class_create:
    cdev_del(&dev1.cdev_test);

err_chr_add:
    unregister_chrdev_region(dev1.dev_num, 1);

err_chrdev:
    return ret;
}

static void usr_exit(void)
{
    unregister_chrdev_region(dev1.dev_num, 1);
    cdev_del(&dev1.cdev_test);

    device_destroy(dev1.class, dev1.dev_num);
    class_destroy(dev1.class);
    printk("bye bye\n");
}

module_init(usr_init);
module_exit(usr_exit);

MODULE_LICENSE("GPL");
MODULE_AUTHOR("fengzc");
MODULE_VERSION("v1.0");

read.c

#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <poll.h>
#include <fcntl.h>
#include <signal.h>

int fd;
char buf1[32] = {0};

static void func(int sigunm)
{
    read(fd, buf1, 32);
    printf("buf is %s\n", buf1);
}

int main(int argc, char *argv[])
{

    int flags;

    fd = open("/dev/test", O_RDWR); // 打开设备节点
    if (fd < 0)
    {
        perror("open error \n");
        return fd;
    }
    signal(SIGIO, func);

    fcntl(fd, F_SETOWN, getpid());

    flags = fcntl(fd, F_GETFL);

    fcntl(fd, F_GETFL, flags | FASYNC);

    while(1);
    close(fd);
    return 0;
}

write.c

#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>

int main(int argc, char *argv[])
{
    int fd;
    char buf1[32] = {0};
    char buf2[32] = {"nihao"};
    fd = open("/dev/test", O_RDWR); // 打开设备节点
    if (fd < 0)
    {
        perror("open error \n");
        return fd;
    }
    printf("write before\n");
    write(fd, buf2, strlen(buf2));
    printf("write after\n");
    close(fd);
    return 0;
}

IO多路复用

Linux 驱动之高级字符设备

通俗说明

D同样也在河边钓鱼,但是D生活水平比较好,D拿了很多的鱼竿,一次性有很多鱼竿在等,D不断的查看每个鱼竿是否有鱼上钩。增加了效率,减少了等待的时间。

IO多路转接是多了一个select函数,select函数有一个参数是文件描述符集合,对这些文件描述符进行循环监听,当某个文件描述符就绪时,就对这个文件描述符进行处理。

IO多路复用的实现

IO多路复用可以实现一个进程监视多个文件描述符,一旦其中一个文件描述符准备就绪。就通知应用程序进行相应的操作。

在应用层,Linux提供了三种IO复用的API函数,分别是select,poll,epoll

在驱动中,需要实现file_operations结构体的poll函数。

应用层IO多路复用API的区别

pollselect基本一样,都可以监听多个文件描述符,通过轮询文件描述符来获取已经准备好的文件描述符。

epoll是将主动轮询变成了被动通知,当事件发生时,被动的接收通知。

应用层的poll函数介绍

函数原型:int poll(struct pollfd *fds, nfds_t nfds,int timeout);

功能:监视并等待多个文件描述符的属性变化。

函数参数:

  • fds:指向struct pollfd结构体,用于指定给定的fd条件。
  • nfds:指定文件描述符fds的个数。
  • timeout:指定等待的时间,单位是ms。无论I/0是否准备好,时间到poll都会返回。如果timeout
    大于0,等待指定的时间。如果timeout等于0,立即返回。如果timeout等于-1,事件发生以后才返回。

返回值:失败返回-1,成功返回revents不为0的文件描述符个数

其中:

struct pollfd{
    int fd;          //被监视的文件描述符
    short events;    //等待的事件
    short revents;   //实际发生的事件
}
事件 描述 是否可作为输入(events) 是否可作为输出(revents)
POLLIN 数据可读(包括普通数据&优先数据 1 1
POLLOUT 数据可写(普通数据&优先数据) 1 1
POLLRDNORM 普通数据可读 1 1
POLLRDBAND 优先级带数据可读(linux不支持) 1 1
POLLPRI 高优先级数据可读,比如TCP带外数据 1 1
POLLWRNORM 普通数据可写 1 1
POLLWRBAND 优先级带数据可写 1 1
POLLRDHUP TCP连接被对端关闭,或者关闭了写操作,由GNU引入 1 1
POPPHUP 挂起 0 1
POLLERR 错误 0 1
POLLNVAL 文件描述符没有打开 0 1
驱动中的poll函数

使用selectpoll等系统调用会触发设备驱动中file_operationspoll()函数被执行。所以需要完善驱动中的poll函数。驱动中的poll函数原型:

// __poll_t是根据不同的体系结构和配置,可能会被定义为不同的基本数据类型,例如 unsigned int 或 long。
__poll_t (*poll) (struct file *, struct poll_table_struct *);

驱动中poll函数要进行两项工作。

第一项工作:对可能引起设备文件状态变化的等待队列调用poll_wait,将对应的等待队列头添加到poll_table

第二项工作:返回表示是否能对设备进行无阻塞读写访问的掩码。

poll_wait原型:

void poll_wait(struct file *filp,wait_queue_head_t *queue,poll_table *wait) ;

注意: poll_wait这个函数是不会引起阻塞的

例子

usr.c

#include <linux/module.h>
#include <linux/init.h>
#include <linux/moduleparam.h>
#include <linux/fs.h>
#include <linux/kdev_t.h>
#include <linux/cdev.h>
#include <linux/device.h>
#include <linux/uaccess.h>
#include <linux/errno.h>

#include <asm/atomic.h>
#include <linux/semaphore.h>

#include <linux/wait.h>
#include <linux/poll.h>

struct device_test
{
    dev_t dev_num;
    struct cdev cdev_test;

    int major;
    int minor;

    struct class *class;
    struct device *device;

    char kbuf[32];
    int flag;
};

struct device_test dev1;

DECLARE_WAIT_QUEUE_HEAD(read_wq);

static int cdev_test_open(struct inode *inode, struct file *file)
{

    file->private_data = &dev1;
    printk("This is a cdev_test_open\n");
    return 0;
}

static ssize_t cdev_test_read(struct file *file, char __user *buf, size_t size, loff_t *off)
{
    struct device_test *dev = (struct device_test *)file->private_data;

    if (file->f_flags & O_NONBLOCK)
    {
        if (dev->flag != 1)
        {
            return -EAGAIN;
        }
    }

    wait_event_interruptible(read_wq, dev->flag);
    if (copy_to_user(buf, dev->kbuf, strlen(dev->kbuf)) != 0)
    {
        printk("copy_to_user is error");
        return -EFAULT;
    }
    return 0;
}

static ssize_t cdev_test_write(struct file *file, const char __user *buf, size_t size, loff_t *off)
{
    struct device_test *dev = (struct device_test *)file->private_data;
    if (copy_from_user(dev->kbuf, buf, size) != 0)
    {
        printk("copy_from_user is error");
        return -EFAULT;
    }
    dev->flag = 1;
    wake_up_interruptible(&read_wq);
    printk("kbuf is %s\n", dev->kbuf);
    return 0;
}

static int cdev_test_release(struct inode *inode, struct file *file)
{
    return 0;
}

static __poll_t cdev_test_poll(struct file *file, struct poll_table_struct *p)
{
    struct device_test *dev = (struct device_test *)file->private_data;

    __poll_t mask = 0;
    if (dev->flag == 1)
    {
        mask |= POLLIN;
    }

    poll_wait(file, &read_wq, p);
    return mask;
}

struct file_operations cdev_test_ops = {
    .owner = THIS_MODULE,
    .open = cdev_test_open,
    .read = cdev_test_read,
    .write = cdev_test_write,
    .release = cdev_test_release,
    .poll = cdev_test_poll,
};

static int usr_init(void)
{
    int ret;

    ret = alloc_chrdev_region(&dev1.dev_num, 0, 1, "alloc_name");
    if (ret < 0)
    {
        goto err_chrdev;
        printk("alloc_chrdev_region failed\n");
    }
    printk("alloc_chrdev_region succeed\n");

    dev1.major = MAJOR(dev1.dev_num);
    dev1.minor = MINOR(dev1.dev_num);
    printk("major is %d\n", dev1.major);
    printk("minor is %d\n", dev1.minor);

    dev1.cdev_test.owner = THIS_MODULE;
    cdev_init(&dev1.cdev_test, &cdev_test_ops);
    ret = cdev_add(&dev1.cdev_test, dev1.dev_num, 1);
    if (ret < 0)
    {
        goto err_chr_add;
    }

    dev1.class = class_create(THIS_MODULE, "test");
    if (IS_ERR(dev1.class))
    {
        goto err_class_create;
    }
    device_create(dev1.class, NULL, dev1.dev_num, NULL, "test");
    if (IS_ERR(dev1.device))
    {
        goto err_class_device;
    }

    dev1.flag = 0;

    return 0;
err_class_device:
    class_destroy(dev1.class);

err_class_create:
    cdev_del(&dev1.cdev_test);

err_chr_add:
    unregister_chrdev_region(dev1.dev_num, 1);

err_chrdev:
    return ret;
}

static void usr_exit(void)
{
    unregister_chrdev_region(dev1.dev_num, 1);
    cdev_del(&dev1.cdev_test);

    device_destroy(dev1.class, dev1.dev_num);
    class_destroy(dev1.class);
    printk("bye bye\n");
}

module_init(usr_init);
module_exit(usr_exit);

MODULE_LICENSE("GPL");
MODULE_AUTHOR("fengzc");
MODULE_VERSION("v1.0");

read.c

#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <poll.h>

int main(int argc, char *argv[])
{
    int fd;
    char buf1[32] = {0};
    char buf2[32] = {"nihao"};
    struct pollfd fds[1];
    int ret;
    fd = open("/dev/test", O_RDWR); // 打开设备节点
    if (fd < 0)
    {
        perror("open error \n");
        return fd;
    }
    fds[0].fd = fd;
    fds[0].events = POLLIN;
    printf("read before\n");
    while (1)
    {
        ret = poll(fds, 1, 3000);
        if (!ret)
        {
            printf("timeout!!\n");
        }
        else if (fds[0].revents & POLLIN)
        {
            ret = read(fd, buf1, sizeof(buf1));
            if (ret < 0)
            {
                perror("read error \n");
                return ret;
            }
            printf("buf1 is %s\n", buf1);
            sleep(1);
        }
    }

    printf("read after\n");
    close(fd);
    return 0;
}

write.c

#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>

int main(int argc, char *argv[])
{
    int fd;
    char buf1[32] = {0};
    char buf2[32] = {"nihao"};
    fd = open("/dev/test", O_RDWR); // 打开设备节点
    if (fd < 0)
    {
        perror("open error \n");
        return fd;
    }
    printf("write before\n");
    write(fd, buf2, strlen(buf2));
    printf("write after\n");
    close(fd);
    return 0;
}

输出:

异步IO

Linux 驱动之高级字符设备

通俗说明

E也想钓鱼,但E有事情,于是他雇来了F,让F帮他等待鱼上钩,一旦有鱼上钩,F就打电话给E,E就会将鱼钓上去。

小结

Linux 驱动之高级字符设备

Linux定时器

Linux内核定时器是一种基于未来时间点的计时方式。基于未来时间点的计时是以当前时刻为计时开始的时间点,以未来的某一时刻为计时的终点。
比如,现在是早上7点,我想在睡一会我就用手机定时五分钟,定时时间就是7点+5分钟=7点5分这种定时方式就和手机闹钟非常类似。

内核定时器的精度不高,所以不能作为高精度定时器使用。并且内核定时器不是周期性运行的,到计时终点后会自动关闭。如果我们想要实现周期性定时,就需要定时处理函数中重新开启定时器。

Linux定时器的表示

Linux内核使用timer_list结构体表示内核定时器,timer_list定义在include/linux/timer.h头文件当中,定义如下:

struct timer_list {
	/*
	 * All fields that change during normal runtime grouped to the
	 * same cacheline
	 */
	struct hlist_node	entry;
	unsigned long		expires;
	void			(*function)(struct timer_list *);
	u32			flags;

#ifdef CONFIG_LOCKDEP
	struct lockdep_map	lockdep_map;
#endif
};

定时时间计算

timer_list结构体中,expires为计时终点的时间。单位是节拍数。节拍数怎么和时间关联上呢?Linux内核中有一个宏HZ,这个宏用来表示一秒钟对应的节拍的数量。利用Linux内核中的这个宏,我们就可以把时间转换成节拍数。比如,定时一秒钟换成节拍数就是expires=jiffies+1*HZ,其中jiffies为系统当前时间对应的节拍数。
HZ的值我们是可以设置的,也就是说一秒钟对应多少个节拍数我们是可以设置的。打开menuconfig图形化配置界面:

->Kernel Features
    -> Timer frequency (<choice> [=y])

全局变量jiffies

全局变量jiffies用来记录自系统启动以来产生的节拍的总数。 启动时, 内核将该变量初始化为 0,此后,每次时钟中断处理程序都会增加该变量的值。 因为一秒内时钟中断的次数为HZ(节拍数),所以jiffies一秒内增加的值也就为HZ(节拍数), 系统运行时间以秒为单位计算, 就等于 jiffies/HZjiffies=seconds*HZjiffies定义在文件include/linux/jiffies.h 中,定义如下:

extern u64 __cacheline_aligned_in_smp jiffies_64;
extern unsigned long volatile __cacheline_aligned_in_smp __jiffy_arch_data jiffies;

全局变量jiffies转换函数

Linux 内核提供了几个 jiffies ms、 us、ns之间的转换函数,如下所示:

函数 作用
int jiffies_to_msecs(const unsigned long j) 将 jiffies 类型的参数 j 转换为对应的毫秒。
int jiffies_to_usecs(const unsigned long j) 将 jiffies 类型的参数 j 转换为对应的微秒。
u64 jiffies_to_nsecs(const unsigned long j) 将 jiffies 类型的参数 j 转换为对应的纳秒。
long msecs_to_jiffies(const unsigned int m) 将毫秒转换为 jiffies 类型。
long usecs_to_jiffies(const unsigned int u) 将微秒转换为 jiffies 类型。
unsigned long nsecs_to_jiffies(u64 n) 将纳秒转换为 jiffies 类型。

举例:定时 10ms
jiffies + msecs_to_jiffies(10)

内核定时器使用步骤

  1. 初始化内核定时器。

    可以直接使用宏#define DEFINE_TIMER(_name, _function, _expires, _data)**(linux 3.x.x,linux 4.x.x版本)**来初始化内核定时器。
    功能:静态定义结构体变量并且初始化初始化function,expires,data 成员。

    参数:

    • _name:变量名

    • _function:超时处理函数

    • expires:到时时间,一般在启动定时前需要重新初始化

    • _data:传递给超时处理函数的参数

    举例:

    static void timer_function(unsigned long data){}
        printk("This is timer function \n");
        mod_timer(&test_timer, jiffies + 1 * HZ);
    }
    DEFINE_TIMER(test_timer, timer_function, 0, 0);
    

    高版本的时候内核**(linux 5.x.x)**当中,这个宏发生了变化,只剩下两个参数。原型:

    #define DEINFE_TIMER(_name, _function)
    

    举例:

    static void timer_function(struct timer_list *t){
        printk("This is timer function \n");
    }
    DEFINE_TIMER(test_timer, timer_function);
    

    或者手动定义timer_list结构体。手动定义如下:

    struct timer_list timer; /* 定义定时器 */
    void function(unsigned long arg){} //定时器超时处理函数
    
    init timer(&timer); /* 初始化定时器 */
    timer.function = function; /* 设置定时处理函数 */
    timer.expires=jffies + msecs_to_jiffies(2000):/* 超时时间 2秒*/
    timer.data =(unsigned long)&dev; /* 将设备结构体作为参数 *
    
  2. 调用add_timer()函数,向 Linux 内核注册定时器,使用add_timer函数向内核注册定时器以后,定时器就会开始运行。
    函数原型: void add_timer(struct timer_list *timer)

  3. 在驱动出口函数调用del_timer()函数,删掉定时器
    函数原型:int del_timer(struct timer_list * timer)
    如果想修改定时时间,调用mod_timer函数修改定时器的值。函数原型: int mod_timer(struct timer_list *timer,unsigned long expires);

例子

helloworld.c

#include <linux/module.h>
#include <linux/init.h>
#include <linux/time.h>

static void test_function(struct timer_list *list){
    printk("Testing test_function");
}

DEFINE_TIMER(test_timer, test_function);

static int helloworld_init(void){
    printk("helloworld!\n");
    test_timer.expires = jiffies_64 + msecs_to_jiffies(5000);
    add_timer(&test_timer);
    return 0;
}

static void helloworld_exit(void){
    printk("helloworld bye\n");
    del_timer(&test_timer);
}

module_init(helloworld_init);
module_exit(helloworld_exit);

MODULE_LICENSE("GPL");
MODULE_AUTHOR("fengzc");
MODULE_VERSION("v1.0");

实现循环定时功能

helloworld.c

#include <linux/module.h>
#include <linux/init.h>
#include <linux/time.h>

static void test_function(struct timer_list *list);

DEFINE_TIMER(test_timer, test_function);

static void test_function(struct timer_list *list)
{
    printk("Testing test_function");
    mod_timer(&test_timer, jiffies_64 + msecs_to_jiffies(1000));
}


static int helloworld_init(void)
{
    printk("helloworld!\n");
    test_timer.expires = jiffies_64 + msecs_to_jiffies(5000);
    add_timer(&test_timer);
    return 0;
}

static void helloworld_exit(void)
{
    printk("helloworld bye\n");
    del_timer(&test_timer);
}

module_init(helloworld_init);
module_exit(helloworld_exit);

MODULE_LICENSE("GPL");
MODULE_AUTHOR("fengzc");
MODULE_VERSION("v1.0");

使用定时器实现秒字符设备

usr.c

#include <linux/module.h>
#include <linux/init.h>
#include <linux/moduleparam.h>
#include <linux/fs.h>
#include <linux/kdev_t.h>
#include <linux/cdev.h>
#include <linux/device.h>
#include <linux/uaccess.h>
#include <linux/errno.h>
#include <linux/time.h>

struct device_test
{
    dev_t dev_num;
    struct cdev cdev_test;

    int major;
    int minor;

    struct class *class;
    struct device *device;

    char kbuf[32];
    atomic64_t counter;
};
struct device_test dev1;

static void timer_function(struct timer_list *list);

DEFINE_TIMER(test_timer, timer_function);

static void timer_function(struct timer_list *list)
{
    printk("Testing test_function");
    mod_timer(&test_timer, jiffies_64 + msecs_to_jiffies(1000));
    atomic64_inc(&dev1.counter);
}


static int cdev_test_open(struct inode *inode, struct file *file)
{
    file->private_data = &dev1;
    printk("This is a cdev_test_open\n");

    test_timer.expires = jiffies_64 + msecs_to_jiffies(1000);
    add_timer(&test_timer);

    atomic64_set(&dev1.counter, 0);
    return 0;
}

static ssize_t cdev_test_read(struct file *file, char __user *buf, size_t size, loff_t *off)
{
    unsigned int counter = 0;
    struct device_test *dev = (struct device_test *)file->private_data;

    counter = atomic64_read(&dev->counter);
    if (copy_to_user(buf, &counter, sizeof(counter)) != 0)
    {
        printk("copy_to_user is error");
        return -EFAULT;
    }
    return 0;
}

static ssize_t cdev_test_write(struct file *file, const char __user *buf, size_t size, loff_t *off)
{
    struct device_test *dev = (struct device_test *)file->private_data;
    if (copy_from_user(dev->kbuf, buf, size) != 0)
    {
        printk("copy_from_user is error");
        return -EFAULT;
    }
    printk("kbuf is %s\n", dev->kbuf);
    return 0;
}

static int cdev_test_release(struct inode *inode, struct file *file)
{
    del_timer(&test_timer);
    return 0;
}

struct file_operations cdev_test_ops = {
    .owner = THIS_MODULE,
    .open = cdev_test_open,
    .read = cdev_test_read,
    .write = cdev_test_write,
    .release = cdev_test_release,
};

static int usr_init(void)
{
    int ret;
    ret = alloc_chrdev_region(&dev1.dev_num, 0, 1, "alloc_name");
    if (ret < 0)
    {
        goto err_chrdev;
        printk("alloc_chrdev_region failed\n");
    }
    printk("alloc_chrdev_region succeed\n");

    dev1.major = MAJOR(dev1.dev_num);
    dev1.minor = MINOR(dev1.dev_num);
    printk("major is %d\n", dev1.major);
    printk("minor is %d\n", dev1.minor);

    dev1.cdev_test.owner = THIS_MODULE;
    cdev_init(&dev1.cdev_test, &cdev_test_ops);
    ret = cdev_add(&dev1.cdev_test, dev1.dev_num, 1);
    if (ret < 0)
    {
        goto err_chr_add;
    }

    dev1.class = class_create(THIS_MODULE, "test");
    if (IS_ERR(dev1.class))
    {
        goto err_class_create;
    }
    device_create(dev1.class, NULL, dev1.dev_num, NULL, "test");
    if (IS_ERR(dev1.device))
    {
        goto err_class_device;
    }

    return 0;
err_class_device:
    class_destroy(dev1.class);

err_class_create:
    cdev_del(&dev1.cdev_test);

err_chr_add:
    unregister_chrdev_region(dev1.dev_num, 1);

err_chrdev:
    return ret;
}

static void usr_exit(void)
{
    unregister_chrdev_region(dev1.dev_num, 1);
    cdev_del(&dev1.cdev_test);

    device_destroy(dev1.class, dev1.dev_num);
    class_destroy(dev1.class);

    del_timer(&test_timer);
    printk("bye bye\n");
}

module_init(usr_init);
module_exit(usr_exit);

MODULE_LICENSE("GPL");
MODULE_AUTHOR("fengzc");
MODULE_VERSION("v1.0");

app.c

#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>

int main(int argc, char *argv[])
{
    int fd;
    unsigned int counter = 0;
    fd = open("/dev/test", O_RDWR); // 打开设备节点
    if (fd < 0)
    {
        perror("open error \n");
        return fd;
    }
    while (1)
    {
        read(fd, &counter, sizeof(counter));
        printf("coutner is %d\n", counter);
        sleep(1);
    }

    return 0;
}

输出:

Linux 驱动之高级字符设备

Linux 内核中打印等级

dmesg命令

作用:dmesg命令可以用来显示打印信息。

用法: dmesg [参数]

常用参数:

  • -C, --clear 清除内核环形缓冲区

  • -c, --read-clear 读取并清除所有消息

  • -T, --显示时间戳

dmesg命令也可以与grep命令组合使用。如查找待用usb关键字的打印信息,就可以使用如下命令:dmesg | grep usb

Linux 内核打印等级

内核日志的打印是有打印等级的。可以通过调整内核的打印等级来控制打印日志的输出。使用命令cat /proc/sys/kernel/printk可以查看默认的打印等级。如下图所示:

fengzc@ubuntu:~$ cat /proc/sys/kernel/printk
4	4	1	7

从左到右:

  • 7代表的是console_loglevel:控制台的日志级别,printk输出的信息优先级高于它才会打印到控制台

  • 4代表的是default_message_loglevel:默认的消息日志级剌,如果printk没有指定优先级,默认的优先级就是它

  • 1代表的是minimum_console_loglevel:最低的控制台日志级别,控制台日志级别可被设置的最小值

  • 7代表的是default_console_loglevel:默认的控制台日志级别,控制台日志级别默认缺省值

这四个等级定义在kernel/printk/printk.c文件当中。如下图所示:

int console_printk[4] = {
        CONSOLE_LOGLEVEL_DEFAULT,       /* console_loglevel */
        MESSAGE_LOGLEVEL_DEFAULT,       /* default_message_loglevel */
        CONSOLE_LOGLEVEL_MIN,           /* minimum_console_loglevel */
        CONSOLE_LOGLEVEL_DEFAULT,       /* default_console_loglevel */
};

内核提供了8中不同的日志级别,分别对应0到7,数字越小级别就越高。定义在include/linux/kern_levels.h文件当中。

#define KERN_EMERG  KERN_SOH "0"    /* system is unusable */
#define KERN_ALERT  KERN_SOH "1"    /* action must be taken immediately */
#define KERN_CRIT   KERN_SOH "2"    /* critical conditions */
#define KERN_ERR    KERN_SOH "3"    /* error conditions */
#define KERN_WARNING    KERN_SOH "4"    /* warning conditions */
#define KERN_NOTICE KERN_SOH "5"    /* normal but significant condition */
#define KERN_INFO   KERN_SOH "6"    /* informational */
#define KERN_DEBUG  KERN_SOH "7"    /* debug-level messages */

在内核打印的时候,只有数值小于(级别高)当前系统的设置的打印等级,打印信息才可以被显示到控制台上,大于或者等于(级别低)的打印信息不会被显示到系统上。

如何修改Linux内核打印等级

  • 方法1:

    通过make menuconfig图形化配置界面修改默认的日志级别。menuconfig图形化配置界面路径:

    Kernel hacking -> printk and dmesg options -> Default message log level()

  • 方法2:在调用printk的时候设置打印等级。如下所示:
    printk (KERN_EMERG"hello! \n");

  • 方法3:使用echo直接修改打印等级。如下所示:

    • 查看内核打印等级:cat /proc/sys/kernel/printk

    • 修改控制台打印等级,如屏蔽所有打印,只需要将第一个数值调整到0即可。使用命令如下:

      echo 0 4 1 7 > /proc/sys/kernel/printk

      打开控制台的所有打印,使用命令:
      echo 7 4 1 7 > /proc/sys/kernel/printk

llseek定位设备驱动

应用中lseek函数的使用

函数原型:off_t lseek(int fd, off_t offset, int whence);

函数参数:

  • fd:要操作的文件描述符。
  • offset:以whence为基准的偏移量(单位是字节)。
  • whence:可以为SEEK_SET (文件指针开头),SEEK_CUR(文件指针当前位置),SEEK_END(文件指针末尾)

返回值:成功返回文件读写指针距离文件开头的字节大小,失败返回-1。

举例:获取文件的长度

ret = lseek (fd, o, SEEK_END);

举例:扩展文件

ret=lseek (fd, 10, SEEK_CUR);

驱动中实现llseek

在驱动中需要完善file_operations结构体中的llseek函数。可看下面的例,通用结构

驱动中read和write编写

如果我们在驱动中实现了llseek函数,驱动中对应的readwrite函数也需要进行完善。在进行readwrite操作的时候,必须先对文件位置进行校准。

驱动中的read函数原型:
ssize_t (*read)(struct file * filp, char _user * buffer, size_t size, loff_t * p);

各个参数意义如下:

  • struct file *file:打开的文件。
  • char _user * buffer:放置数据的缓冲区。size_t size:要读取的数据长度。
  • loff_t * p:读的位置,也就是相对于文件的开头的偏移。在读完数据以后,这个指针要进行移动,移动的值为读取信息的长度。

驱动中的write函数原型:

ssize t (*write) (struct file *filp, const char user *buffer, size t count, loff t *ppos);

各个参数意义如下:

  • struct file *file:打开的文件。
  • char user * buffer:写入数据的缓冲区。
  • size t size:要写入的数据长度。
  • loff_t *ppos:写的位置,也就是相对于文件的开头的偏移

例子

usr.c

#include <linux/module.h>
#include <linux/init.h>
#include <linux/moduleparam.h>
#include <linux/fs.h>
#include <linux/kdev_t.h>
#include <linux/cdev.h>
#include <linux/device.h>
#include <linux/uaccess.h>
#include <linux/errno.h>
#include <linux/time.h>

#define BUFSIZE 1024
static char mem[BUFSIZE] = {0};
struct device_test
{
    dev_t dev_num;
    struct cdev cdev_test;

    int major;
    int minor;

    struct class *class;
    struct device *device;

    char kbuf[32];
};
struct device_test dev1;

static int cdev_test_open(struct inode *inode, struct file *file)
{
    file->private_data = &dev1;
    printk("This is a cdev_test_open\n");

    return 0;
}

static ssize_t cdev_test_read(struct file *file, char __user *buf, size_t size, loff_t *off)
{
    struct device_test *dev = (struct device_test *)file->private_data;
    loff_t p = *off;
    size_t count = size;
    if (p > BUFSIZE)
    {
        return 0;
    }
    if (count > BUFSIZE - p)
    {
        count = BUFSIZE - p;
    }

    if (copy_to_user(buf, mem + p, count))
    {
        printk("copy_to_user error\n");
        return -1;
    }
    int i;
    for (i = 0; i < 20; i++)
    {
        printk("buf[%d] is %c\n", i, mem[i]);
    }

    printk("mem is %s,p is %llu, count is %ld\n", mem + p, p, count);
    *off += count;
    return count;
}

static ssize_t cdev_test_write(struct file *file, const char __user *buf, size_t size, loff_t *off)
{
    loff_t p = *off;
    size_t count = size;
    if (p > BUFSIZE)
    {
        return 0;
    }
    if (count > BUFSIZE - p)
    {
        count = BUFSIZE - p;
    }
    if (copy_from_user(mem + p, buf, count) != 0)
    {
        printk("copy_from_user is error");
        return -EFAULT;
    }
    printk("mem is %s, p is %llu\n", mem + p, p);
    *off += count;
    return count;
}

static int cdev_test_release(struct inode *inode, struct file *file)
{
    return 0;
}

static loff_t cdev_test_llseek(struct file *file, loff_t offset, int whence)
{
    loff_t new_offset;
    switch (whence)
    {
    case SEEK_SET:
        if (offset < 0)
        {
            return -EINVAL;
            break;
        }
        if (offset > BUFSIZE)
        {
            return -EINVAL;
            break;
        }
        new_offset = offset;
        break;
    case SEEK_CUR:
        if ((file->f_pos + offset) > BUFSIZE)
        {
            return -EINVAL;
            break;
        }
        if ((file->f_pos + offset) < 0)
        {
            return -EINVAL;
            break;
        }
        new_offset = file->f_pos + offset;
        break;
    case SEEK_END:
        if ((file->f_pos + offset) < 0)
        {
            return -EINVAL;
            break;
        }
        new_offset = BUFSIZE + offset;
        break;
    default:
        return -EINVAL;
    }
    file->f_pos = new_offset;
    return new_offset;
}

struct file_operations cdev_test_ops = {
    .owner = THIS_MODULE,
    .open = cdev_test_open,
    .read = cdev_test_read,
    .write = cdev_test_write,
    .release = cdev_test_release,
    .llseek = cdev_test_llseek,
};

static int usr_init(void)
{
    int ret;
    ret = alloc_chrdev_region(&dev1.dev_num, 0, 1, "alloc_name");
    if (ret < 0)
    {
        goto err_chrdev;
        printk("alloc_chrdev_region failed\n");
    }
    printk("alloc_chrdev_region succeed\n");

    dev1.major = MAJOR(dev1.dev_num);
    dev1.minor = MINOR(dev1.dev_num);
    printk("major is %d\n", dev1.major);
    printk("minor is %d\n", dev1.minor);

    dev1.cdev_test.owner = THIS_MODULE;
    cdev_init(&dev1.cdev_test, &cdev_test_ops);
    ret = cdev_add(&dev1.cdev_test, dev1.dev_num, 1);
    if (ret < 0)
    {
        goto err_chr_add;
    }

    dev1.class = class_create(THIS_MODULE, "test");
    if (IS_ERR(dev1.class))
    {
        goto err_class_create;
    }
    device_create(dev1.class, NULL, dev1.dev_num, NULL, "test");
    if (IS_ERR(dev1.device))
    {
        goto err_class_device;
    }

    return 0;
err_class_device:
    class_destroy(dev1.class);

err_class_create:
    cdev_del(&dev1.cdev_test);

err_chr_add:
    unregister_chrdev_region(dev1.dev_num, 1);

err_chrdev:
    return ret;
}

static void usr_exit(void)
{
    unregister_chrdev_region(dev1.dev_num, 1);
    cdev_del(&dev1.cdev_test);

    device_destroy(dev1.class, dev1.dev_num);
    class_destroy(dev1.class);

    printk("bye bye\n");
}

module_init(usr_init);
module_exit(usr_exit);

MODULE_LICENSE("GPL");
MODULE_AUTHOR("fengzc");
MODULE_VERSION("v1.0");

app.c

#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>

int main(int argc, char *argv[])
{
    int fd;
    int ret;
    unsigned int counter = 0;
    fd = open("/dev/test", O_RDWR); // 打开设备节点
    if (fd < 0)
    {
        perror("open error \n");
        return fd;
    }
    write(fd, "hello world ", 13);

    ret = lseek(fd, 0, SEEK_CUR);
    printf("ret:%d\n", ret);

    ret = lseek(fd, 0, SEEK_SET);
    printf("ret:%d\n", ret);

    char readbuf[13] = {0};
    read(fd, readbuf, sizeof(readbuf));
    printf("read:%s\n", readbuf);

    ret = lseek(fd, 0, SEEK_CUR);
    printf("ret:%d\n", ret);

    ret = lseek(fd, -1, SEEK_CUR);
    printf("ret:%d\n", ret);

    write(fd, "Linux", 6);
    ret = lseek(fd, 0, SEEK_CUR);
    printf("ret:%d\n", ret);

    ret = lseek(fd, 0, SEEK_SET);
    printf("ret:%d\n", ret);

    char readbuf1[19] = {0};
    read(fd, readbuf1, sizeof(readbuf1));
    printf("read:%s\n", readbuf1);
    ret = lseek(fd, 0, SEEK_CUR);

    printf("ret:%d\n", ret);
    close(fd);

    return 0;
}

ioctl设备操作

ioctl设备操作的作用

用户如果要对外设驱动进行操作,设备驱动不仅要具备读写的能力,还要具备对硬件的控制能立。比如,LED灯的点亮,串口波特率的修改等等。write/read也可以用来控制设备,但是write/read主要是数据流对数据的操作。显然用来操作设备是不太合适的。这些控制操作通常需要非数据的操作,即通过ioctl操作来实现。

file_operation文件操作集中ioctl函数实现

函数原型:

long(*unlocked_ioctl)(struct file *file,unsigned int cmd, unsigned long arg);

参数:

  • struct file *file: file结构体,即打开字符设备的进程创建的结构体。
  • unsigned int cmd:用户空间传递的命令。
  • unsigned long arg:配合cmd用的参数。

返回值:成功返回0,失败返回负值。、

应用层ioctl系统调用

头文件:#include <sys/ioctl.h>

函数原型:int ioctl(int fd, unsigned long request, ...);

参数:

  • fd:打开设备节点获得的文件描述符。
  • cmd:给驱动传递的命令
  • ...:可变参数

返回值:成功返回0,失败返-1。

应用层ioctl系统调用和驱动unlocked_ioctl调用关系

Linux 驱动之高级字符设备

ioctl命令的构成

需要注意的是,用户程序只是通过命令来告诉驱动程序他要做什么事情,至于这个命令对应什么事情则是由驱动程序自己来决定的,也就是攻城狮来决定的。

一个cmd由32位组成 :

Linux 驱动之高级字符设备

Linux 驱动之高级字符设备

ioctl命令的合成宏

// 合成没有数据传递的命令
#define _IO(type,nr)		_IOC(_IOC_NONE,(type),(nr),0)
// 合成从驱动中读取数据的命令
#define _IOR(type,nr,size)	_IOC(_IOC_READ,(type),(nr),(_IOC_TYPECHECK(size)))
// 合成向驱动中写数据的命令
#define _IOW(type,nr,size)	_IOC(_IOC_WRITE,(type),(nr),(_IOC_TYPECHECK(size)))
// 合成先写入数据再读取数据的命令
#define _IOWR(type,nr,size)	_IOC(_IOC_READ|_IOC_WRITE,(type),(nr),(_IOC_TYPECHECK(size)))
// 参数: type:   命令的类型
//      nr:     命令的序列号
//      size:   命令的大小

ioctl命令的分解宏

// 分解没有数据传递的命令
#define _IOC_DIR(nr)		(((nr) >> _IOC_DIRSHIFT) & _IOC_DIRMASK)
// 分解从驱动中读取数据的命令
#define _IOC_TYPE(nr)		(((nr) >> _IOC_TYPESHIFT) & _IOC_TYPEMASK)
// 分解向驱动中写数据的命令
#define _IOC_NR(nr)		(((nr) >> _IOC_NRSHIFT) & _IOC_NRMASK)
// 分解先写入数据再读取数据的命令
#define _IOC_SIZE(nr)		(((nr) >> _IOC_SIZESHIFT) & _IOC_SIZEMASK)

例子1

app.c

#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/ioctl.h>

#define CMD_TEST0 _IO('L', 0)
#define CMD_TEST1 _IO('L', 1)
#define CMD_TEST2 _IO('A', 0)

#define CMD_TEST3 _IOW('A', 1, int)
#define CMD_TEST4 _IOR('A', 2, int)

#define CMD_TEST5 _IOWR('A', 3, int)

int main(int argc, char *argv[])
{
    printf("CMD_TEST3 type is %ld\n", _IOC_TYPE(CMD_TEST3));
    printf("CMD_TEST3 dir is %ld\n", _IOC_DIR(CMD_TEST3));
    printf("CMD_TEST3 nr is %ld\n", _IOC_NR(CMD_TEST3));
    printf("CMD_TEST3 size is %ld\n", _IOC_SIZE(CMD_TEST3));
    return 0;
}

例子2

app.c

#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/ioctl.h>

#define CMD_TEST0 _IO('L', 0)
#define CMD_TEST1 _IOW('L', 1, int)
#define CMD_TEST2 _IOR('L', 2, int)

int main(int argc, char *argv[])
{
    int fd;
    int val;
    unsigned int ret = 0;
    fd = open("/dev/test", O_RDWR); // 打开设备节点
    if (fd < 0)
    {
        perror("open error \n");
        return fd;
    }

    while (1)
    {
        ioctl(fd, CMD_TEST0);
        ioctl(fd, CMD_TEST1, 1);
        ioctl(fd, CMD_TEST2, &val);
        sleep(1);
    }

    close(fd);

    return 0;
}

usr.c

#include <linux/module.h>
#include <linux/init.h>
#include <linux/moduleparam.h>
#include <linux/fs.h>
#include <linux/kdev_t.h>
#include <linux/cdev.h>
#include <linux/device.h>
#include <linux/uaccess.h>
#include <linux/errno.h>
#include <linux/time.h>
#include <linux/ioctl.h>

#define CMD_TEST0 _IO('L', 0)
#define CMD_TEST1 _IOW('L', 1, int)
#define CMD_TEST2 _IOR('L', 2, int)

#define BUFSIZE 1024
static char mem[BUFSIZE] = {0};
struct device_test
{
    dev_t dev_num;
    struct cdev cdev_test;

    int major;
    int minor;

    struct class *class;
    struct device *device;

    char kbuf[32];
};
struct device_test dev1;

static int cdev_test_open(struct inode *inode, struct file *file)
{
    file->private_data = &dev1;
    printk("This is a cdev_test_open\n");

    return 0;
}

static ssize_t cdev_test_read(struct file *file, char __user *buf, size_t size, loff_t *off)
{
    struct device_test *dev = (struct device_test *)file->private_data;
    loff_t p = *off;
    size_t count = size;
    if (p > BUFSIZE)
    {
        return 0;
    }
    if (count > BUFSIZE - p)
    {
        count = BUFSIZE - p;
    }

    if (copy_to_user(buf, mem + p, count))
    {
        printk("copy_to_user error\n");
        return -1;
    }
    int i;
    for (i = 0; i < 20; i++)
    {
        printk("buf[%d] is %c\n", i, mem[i]);
    }

    printk("mem is %s,p is %llu, count is %ld\n", mem + p, p, count);
    *off += count;
    return count;
}

static ssize_t cdev_test_write(struct file *file, const char __user *buf, size_t size, loff_t *off)
{
    loff_t p = *off;
    size_t count = size;
    if (p > BUFSIZE)
    {
        return 0;
    }
    if (count > BUFSIZE - p)
    {
        count = BUFSIZE - p;
    }
    if (copy_from_user(mem + p, buf, count) != 0)
    {
        printk("copy_from_user is error");
        return -EFAULT;
    }
    printk("mem is %s, p is %llu\n", mem + p, p);
    *off += count;
    return count;
}

static int cdev_test_release(struct inode *inode, struct file *file)
{
    return 0;
}

static loff_t cdev_test_llseek(struct file *file, loff_t offset, int whence)
{
    loff_t new_offset;
    switch (whence)
    {
    case SEEK_SET:
        if (offset < 0)
        {
            return -EINVAL;
            break;
        }
        if (offset > BUFSIZE)
        {
            return -EINVAL;
            break;
        }
        new_offset = offset;
        break;
    case SEEK_CUR:
        if ((file->f_pos + offset) > BUFSIZE)
        {
            return -EINVAL;
            break;
        }
        if ((file->f_pos + offset) < 0)
        {
            return -EINVAL;
            break;
        }
        new_offset = file->f_pos + offset;
        break;
    case SEEK_END:
        if ((file->f_pos + offset) < 0)
        {
            return -EINVAL;
            break;
        }
        new_offset = BUFSIZE + offset;
        break;
    default:
        return -EINVAL;
    }
    file->f_pos = new_offset;
    return new_offset;
}

static long cdev_test_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
{
    int val;
    switch (cmd)
    {
    case CMD_TEST0:
        printk("This is CMD_TEST0\n");
        break;
    case CMD_TEST1:
        printk("This is CMD_TEST1\n");
        printk("arg is %ld\n", arg);
        break;
    case CMD_TEST2:
        val = 1;
        if (copy_to_user((int *)arg, &val, sizeof(val)) != 0)
        {
            printk("copy_to_user error\n");
            return -1;
        }
        break;
    default:
        break;
    }
    return 0;
}

struct file_operations cdev_test_ops = {
    .owner = THIS_MODULE,
    .open = cdev_test_open,
    .read = cdev_test_read,
    .write = cdev_test_write,
    .release = cdev_test_release,
    .llseek = cdev_test_llseek,
    .unlocked_ioctl = cdev_test_ioctl,
};

static int usr_init(void)
{
    int ret;
    ret = alloc_chrdev_region(&dev1.dev_num, 0, 1, "alloc_name");
    if (ret < 0)
    {
        goto err_chrdev;
        printk("alloc_chrdev_region failed\n");
    }
    printk("alloc_chrdev_region succeed\n");

    dev1.major = MAJOR(dev1.dev_num);
    dev1.minor = MINOR(dev1.dev_num);
    printk("major is %d\n", dev1.major);
    printk("minor is %d\n", dev1.minor);

    dev1.cdev_test.owner = THIS_MODULE;
    cdev_init(&dev1.cdev_test, &cdev_test_ops);
    ret = cdev_add(&dev1.cdev_test, dev1.dev_num, 1);
    if (ret < 0)
    {
        goto err_chr_add;
    }

    dev1.class = class_create(THIS_MODULE, "test");
    if (IS_ERR(dev1.class))
    {
        goto err_class_create;
    }
    device_create(dev1.class, NULL, dev1.dev_num, NULL, "test");
    if (IS_ERR(dev1.device))
    {
        goto err_class_device;
    }

    return 0;
err_class_device:
    class_destroy(dev1.class);

err_class_create:
    cdev_del(&dev1.cdev_test);

err_chr_add:
    unregister_chrdev_region(dev1.dev_num, 1);

err_chrdev:
    return ret;
}

static void usr_exit(void)
{
    unregister_chrdev_region(dev1.dev_num, 1);
    cdev_del(&dev1.cdev_test);

    device_destroy(dev1.class, dev1.dev_num);
    class_destroy(dev1.class);

    printk("bye bye\n");
}

module_init(usr_init);
module_exit(usr_exit);

MODULE_LICENSE("GPL");
MODULE_AUTHOR("fengzc");
MODULE_VERSION("v1.0");

例子3(地址传参)

// 应用
#define CMD_TEST1 _IOW('L', 1, int)
struct args
{
    int a;
    int b;
    int c;
};

int
main(int argc, char *argv[])
{
    int fd;
    struct args test;
    test.a = 1;
    test.b = 0;
    test.c = 1;
    fd = open("/dev/test", O_RDWR); // 打开设备节点
    if (fd < 0)
    {
        perror("open error \n");
        return fd;
    }

    while (1)
    {
        ioctl(fd, CMD_TEST1, &test);
        sleep(1);
    }

    close(fd);

    return 0;
}

// 驱动
static long cdev_test_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
{
    int val;
    struct args test;
    switch (cmd)
    {
    case CMD_TEST0:
        printk("This is CMD_TEST0\n");
        break;
    case CMD_TEST1:
        printk("This is CMD_TEST1\n");
        if (copy_from_user(&test, arg, sizeof(test)) != 0)
        {
            printk("copy_from_user error\n");
            return -1;
        }
        printk("arg is %d\n", test.a);
        printk("arg is %d\n", test.b);
        printk("arg is %d\n", test.c);
        break;
    case CMD_TEST2:
        val = 1;
        if (copy_to_user((int *)arg, &val, sizeof(val)) != 0)
        {
            printk("copy_to_user error\n");
            return -1;
        }
        break;
    default:
        break;
    }
    return 0;
}

例子4(控制定时器)

usr.c

#include <linux/module.h>
#include <linux/init.h>
#include <linux/moduleparam.h>
#include <linux/fs.h>
#include <linux/kdev_t.h>
#include <linux/cdev.h>
#include <linux/device.h>
#include <linux/uaccess.h>
#include <linux/errno.h>
#include <linux/time.h>

#define TIME_CLOSE_CMD _IO('L', 0)
#define TIME_OPEN_CMD _IO('L', 1)
#define TIME_SET_CMD _IOW('L', 3, int)

struct device_test
{
    dev_t dev_num;
    struct cdev cdev_test;

    int major;
    int minor;

    struct class *class;
    struct device *device;

    char kbuf[32];
    int counter;
};
struct device_test dev1;

static void timer_function(struct timer_list *list);

DEFINE_TIMER(test_timer, timer_function);

static void timer_function(struct timer_list *list)
{
    printk("Testing test_function");
    mod_timer(&test_timer, jiffies_64 + msecs_to_jiffies(dev1.counter));
}

static int cdev_test_open(struct inode *inode, struct file *file)
{
    file->private_data = &dev1;
    printk("This is a cdev_test_open\n");
    return 0;
}

static ssize_t cdev_test_read(struct file *file, char __user *buf, size_t size, loff_t *off)
{
    struct device_test *dev = (struct device_test *)file->private_data;
    return 0;
}

static ssize_t cdev_test_write(struct file *file, const char __user *buf, size_t size, loff_t *off)
{
    struct device_test *dev = (struct device_test *)file->private_data;
    if (copy_from_user(dev->kbuf, buf, size) != 0)
    {
        printk("copy_from_user is error");
        return -EFAULT;
    }
    printk("kbuf is %s\n", dev->kbuf);
    return 0;
}

static int cdev_test_release(struct inode *inode, struct file *file)
{
    del_timer(&test_timer);
    return 0;
}

static long cdev_test_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
{
    struct device_test *dev = (struct device_test *)file->private_data;
    switch (cmd)
    {
    case TIME_CLOSE_CMD:
        del_timer(&test_timer);
        break;
    case TIME_OPEN_CMD:
        add_timer(&test_timer);
        break;
    case TIME_SET_CMD:
        dev->counter = arg;
        test_timer.expires = jiffies_64 + msecs_to_jiffies(dev->counter);
        break;
    default:
        break;
    }
    return 0;
}

struct file_operations cdev_test_ops = {
    .owner = THIS_MODULE,
    .open = cdev_test_open,
    .read = cdev_test_read,
    .write = cdev_test_write,
    .release = cdev_test_release,
    .unlocked_ioctl = cdev_test_ioctl,
};

static int usr_init(void)
{
    int ret;
    ret = alloc_chrdev_region(&dev1.dev_num, 0, 1, "alloc_name");
    if (ret < 0)
    {
        goto err_chrdev;
        printk("alloc_chrdev_region failed\n");
    }
    printk("alloc_chrdev_region succeed\n");

    dev1.major = MAJOR(dev1.dev_num);
    dev1.minor = MINOR(dev1.dev_num);
    printk("major is %d\n", dev1.major);
    printk("minor is %d\n", dev1.minor);

    dev1.cdev_test.owner = THIS_MODULE;
    cdev_init(&dev1.cdev_test, &cdev_test_ops);
    ret = cdev_add(&dev1.cdev_test, dev1.dev_num, 1);
    if (ret < 0)
    {
        goto err_chr_add;
    }

    dev1.class = class_create(THIS_MODULE, "test");
    if (IS_ERR(dev1.class))
    {
        goto err_class_create;
    }
    device_create(dev1.class, NULL, dev1.dev_num, NULL, "test");
    if (IS_ERR(dev1.device))
    {
        goto err_class_device;
    }

    return 0;
err_class_device:
    class_destroy(dev1.class);

err_class_create:
    cdev_del(&dev1.cdev_test);

err_chr_add:
    unregister_chrdev_region(dev1.dev_num, 1);

err_chrdev:
    return ret;
}

static void usr_exit(void)
{
    unregister_chrdev_region(dev1.dev_num, 1);
    cdev_del(&dev1.cdev_test);

    device_destroy(dev1.class, dev1.dev_num);
    class_destroy(dev1.class);

    del_timer(&test_timer);
    printk("bye bye\n");
}

module_init(usr_init);
module_exit(usr_exit);

MODULE_LICENSE("GPL");
MODULE_AUTHOR("fengzc");
MODULE_VERSION("v1.0");

app.c

#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/ioctl.h>

#define TIME_CLOSE_CMD _IO('L', 0)
#define TIME_OPEN_CMD _IO('L', 1)
#define TIME_SET_CMD _IOW('L', 3, int)

int main(int argc, char *argv[])
{
    int fd;
    unsigned int counter = 0;
    fd = open("/dev/test", O_RDWR); // 打开设备节点
    if (fd < 0)
    {
        perror("open error \n");
        return fd;
    }
    ioctl(fd, TIME_SET_CMD, 1000);
    ioctl(fd, TIME_OPEN_CMD);
    sleep(3);
    ioctl(fd, TIME_SET_CMD, 3000);
    sleep(7);
    ioctl(fd, TIME_CLOSE_CMD);
    return 0;
}

封装驱动提供的API函数

timelib.h

#ifndef _TIMELIB_H_
#define _TIMELIB_H_

#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/ioctl.h>
#include <stdlib.h>

#define TIME_CLOSE_CMD _IO('L', 0)
#define TIME_OPEN_CMD _IO('L', 1)
#define TIME_SET_CMD _IOW('L', 3, int)

int time_open(int fd);
int time_close(int fd);
int time_set(int fd,int arg);

#endif

timeopen.c

#include <stdio.h>
#include "timelib.h"
int time_open(int fd)
{
    int ret = 0;
    ret = ioctl(fd, TIME_OPEN_CMD);
    if (ret)
    {
        printf("time_open error\n");
        exit(-1);
    }
    return ret;
}

timeclose.c

#include <stdio.h>
#include "timelib.h"
int time_close(int fd)
{
    int ret = 0;
    ret = ioctl(fd, TIME_CLOSE_CMD);
    if (ret)
    {
        printf("time_close error\n");
        exit(-1);
    }
    return ret;
}

timeset.c

#include <stdio.h>
#include "timelib.h"
int time_set(int fd,int arg)
{
    int ret = 0;
    ret = ioctl(fd, TIME_SET_CMD, arg);
    if (ret)
    {
        printf("time_set error\n");
        exit(-1);
    }
    return ret;
}

app.c

#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/ioctl.h>
#include "timelib.h"


int main(int argc, char *argv[])
{
    int fd;
    unsigned int counter = 0;
    fd = open("/dev/test", O_RDWR); // 打开设备节点
    if (fd < 0)
    {
        perror("open error \n");
        return fd;
    }
    time_set(fd, 1000);
    time_open(fd);
    sleep(3);
    time_set(fd, 3000);
    sleep(7);
    time_close(fd);
    return 0;
}

编写驱动文档

一.Xx驱动介绍

1.1驱动文件所在位置

1.2驱动架构

1.3设备树配置

1.4内核配置

二.xx驱动使用

2.1驱动编译

2.2 API接口介绍

2.3应用编写demo

优化驱动的稳定性和效率

access_ok函数

作用:

检查用户空间指针是否可用

函数原型:access_ok (addr,size);

函数参数:

  • addr:用户空间的指针变量,其指向一个要检查的内存块的开始。
  • size: 要检查的内存块的大小。

函数返回值:如果检查用户空间的内存块可用,则返回真,否则返回假。

unlikely和likely函数

作用:

对代码运行效率有要求的if-elseif分支就应该使用likely或unlikely优化选项,其中:

if (likely(value))等价于if (value)

if (unlikely(value))等价于if (value)

使用场合:

现在的CPU都有ICache和流水线机制。运行当前的指令时,ICache会预读取后面的指令,从而提升效率但是如果条件分支的结果是跳转到了其他指令,那预取下一条指令就浪费时间了。如果使用likely和unlikely来让编译器总是将大概率执行的代码放在靠前的位置,就可以提供效率。
举例;

if (unlikely(value)){

}else {

}
static long cdev_test_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
{
    int val;
    struct args test;
    int len = 0;
    if (_IOC_TYPE(cmd) == 'L')
    {
        printk("This is type  error\n");
        return -1;
    }
    switch (cmd)
    {
    case CMD_TEST0:
        if (_IOC_DIR(cmd) == _IOC_WRITE)
        {
            printk("This is dir error\n");
            return -1;
        }
        printk("This is CMD_TEST0\n");
        break;
    case CMD_TEST1:
        printk("This is CMD_TEST1\n");
        len = sizeof(struct args);
        if (!access_ok(arg, len))
        {
            return -1;
        }
        if (unlikcly(copy_from_user(&test, arg, sizeof(test)) != 0))
        {
            printk("copy_from_user error\n");
            return -1;
        }
        printk("arg is %d\n", test.a);
        printk("arg is %d\n", test.b);
        printk("arg is %d\n", test.c);
        break;
    case CMD_TEST2:
        val = 1;
        if (copy_to_user((int *)arg, &val, sizeof(val)) != 0)
        {
            printk("copy_to_user error\n");
            return -1;
        }
        break;
    default:
        break;
    }
    return 0;
}

驱动调试方法

printk函数

dump_stack函数

作用:打印内核调用堆栈,并打印函数的调用关系。

WARN(condition, fmt… .),WARN_ON(condition)

函数作用:打印函数的调用关系。

BUG,BUG_ON(condition)函数

作用:触发内核的OOPS,输出打印。

panic (fmt. . .)

作用:函数会造成系统死机并输出打印文章来源地址https://www.toymoban.com/news/detail-412696.html

#include <linux/module.h>
#include <linux/init.h>

static int helloworld_init(void)
{
    printk("helloworld!\n");
    // dump_stack();
    // WARN_ON(1);
    // BUG();
    panic("!!!!!!!!!!!!!\n");
    return 0;
}

static void helloworld_exit(void)
{
    printk("helloworld bye\n");
}

module_init(helloworld_init);
module_exit(helloworld_exit);

MODULE_LICENSE("GPL");
MODULE_AUTHOR("fengzc");
MODULE_VERSION("v1.0");

到了这里,关于Linux 驱动之高级字符设备的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • Linux 驱动学习笔记 ——(1)字符设备驱动

    《【正点原子】I.MX6U嵌入式Linux驱动开发指南》学习笔记 字符设备是 Linux 驱动中最基本的一类设备驱动,字节设备就是按照字节流来读写的设备,常见的字符设备包括:LED、蜂鸣器、按键、I2C 以及 SPI 等。 Linux 中一切皆文件,字符设备驱动加载成功后会在 /dev 目录下生成相

    2024年02月08日
    浏览(56)
  • Linux -- 字符设备驱动--LED的驱动开发(初级框架)

    看原理图确定引脚,确定引脚输出什么电平才能点亮 / 熄灭 LED 看主芯片手册,确定寄存器操作方法:哪些寄存器?哪些位?地址是? 编写驱动:先写框架,再写硬件操作的代码 注意 :在芯片手册中确定的寄存器地址被称为 物理地址 ,在 Linux 内核中无法直接使用。 需要使

    2024年04月28日
    浏览(37)
  • LDD学习笔记 -- Linux字符设备驱动

    字符驱动程序用于与Linux内核中的设备进行交互; 字符设备指的是像内存区域这样的硬件组件,通常称为伪设备; 用户空间应用程序通常使用 open read write 等系统调用与这些设备通信; 把用户空间的系统调用连接到设备驱动的系统调用实现方法上。 内核的虚拟文件系统 vir

    2024年02月02日
    浏览(45)
  • Linux下字符设备驱动开发以及流程介绍

    首先我们介绍一下什么是字符设备,然后讲解一下字符设备开发的具体的流程,分别详细介绍每一个流程中涉及到的结构体以及知识点,最后我们编写代码实现字符设备的开发以及测试。 Linux内核设计哲学是把所有的东西都抽象成文件进行访问,这样对设备的访问都是通过文

    2024年02月01日
    浏览(41)
  • Linux设备驱动开发学习笔记(等待队列,锁,字符驱动程序,设备树,i2C...)

    container_of函数可以通过结构体的成员变量检索出整个结构体 函数原型: 内核开发者只实现了循环双链表,因为这个结构能够实现FIFO和LIFO,并且内核开发者要保持最少代码。 为了支持链表,代码中要添加的头文件是linux/list.h。内核中链表实现核心部分的数据结构 是struct li

    2024年01月22日
    浏览(55)
  • Linux字符设备驱动(设备文件,用户空间与内核空间进行数据交互,ioctl接口)

    在Linux系统中“一切皆文件”,上一篇讲述了cdev结构体就描述了一个字符设备驱动,主要包括设备号和操作函数集合。但是要怎么操作这个驱动呢?例如,使用open()该打开谁,read()该从哪读取数据等等。所以就需要创建一个设备文件来代表设备驱动。 应用程序要操纵外部硬件

    2024年02月12日
    浏览(41)
  • 【linux驱动开发】在linux内核中注册一个杂项设备与字符设备以及内核传参的详细教程

    开发环境: windows + ubuntu18.04 + 迅为rk3568开发板 相较于字符设备,杂项设备有以下两个优点: 节省主设备号:杂项设备的主设备号固定为 10,在系统中注册多个 misc 设备驱动时,只需使用子设备号进行区分即可。 使用简单:相比如普通的字符设备驱动, misc驱动只需要将基本信

    2024年01月21日
    浏览(51)
  • Linux驱动开发实战(一)——设备驱动模型

    在早期的Linux内核中并没有为设备驱动提供统一的设备模型。随着内核的不断扩大及系统更加复杂,编写一个驱动程序越来越困难,所以在Linux2.6内核中添加了一个统一的设备模型。这样,写设备驱动程序就稍微容易一些了。本章将对设备模型进行详细的介绍。 设备驱动模型

    2024年02月16日
    浏览(51)
  • Linux设备驱动模型(二)

    基于linux-3.14.16 设备模型(LDM)包括,总线、驱动、设备 以i2c总线为例,下面基本表现出了注册一个总线的过程。 1、定义一个总线bus_type,填充几个回调 其中几个比较重要 , match,总线设备和总线驱动的匹配规则 probe,总线设备和总线驱动匹配后将会执行的回调 2、调用b

    2024年02月05日
    浏览(54)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包