linux驱动开发-简单的字符设备驱动开发实例

一、开发环境说明

我使用的是ubuntu 20.04桌面系统,在vmare虚机中运行。系统中已经安装gcc编译器:

sudo apt-get install gcc

如果系统无法上网,需要事先下载好gcc压缩包,传入系统中,然后解压。

二、实例

本实例大部分参照博客Linux设备驱动程序——一个简单的字符设备驱动程序_Alex-wu的博客-CSDN博客

原博客有关键的操作步骤没有讲到,我这里补充完整。

1、写驱动代码

char.c源代码

#include <linux/module.h>
#include <linux/fs.h>
#include <linux/init.h>
#include <linux/cdev.h>
#include <linux/slab.h>
#include <linux/uaccess.h>
 
#include <linux/device.h>
 
#define MAX_SIZE    0x1000
#define MEM_CLEAR         0x1
 
static struct class *class;
static struct device *dev;
 
 
struct chardev_dev           //定义设备
{
    struct cdev cdev;
    unsigned char mem[MAX_SIZE];
};
 
struct chardev_dev *devp;    
dev_t devno;                      //设备号
 
 
static int chardev_open(struct inode *inode, struct file *filp)   //open函数
{
    filp->private_data = devp;
    return 0;
}
 
 
static int chardev_release(struct inode *inode, struct file *filp)  //release函数
{
    return 0;
}
 
 
static long chardev_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)   //ioctl函数
{
    struct chardev_dev *dev = filp->private_data;
    
    switch (cmd)
    {
    case MEM_CLEAR:
        memset(dev->mem, 0, MAX_SIZE);
        printk(KERN_INFO "chardev is set to zero\n");
        break;
    
    default:
        return -EINVAL;
    }
    
    return 0;
}
 
static ssize_t chardev_read(struct file *filp, char __user *buf, size_t size, loff_t *ppos)    //read函数
{
    unsigned long p = *ppos;
    unsigned int count = size;
    int ret = 0;
    struct chardev_dev *dev = filp->private_data;
    
    if (p >= MAX_SIZE)
        return -EINVAL;
    
    if (count > MAX_SIZE - p)
        count = MAX_SIZE - p;
        
    if (copy_to_user(buf, dev->mem + p, count))                             //从内核空间得到数据
        ret = -EINVAL;
    else
    {
        *ppos += count;
        ret = count;
        printk(KERN_INFO "read %u byte(s) from %lu\n", count, p);
    }
    
    return ret;
}
 
 
static ssize_t chardev_write(struct file *filp, const char __user *buf, size_t size,             //write函数
                    loff_t *ppos)
{
    unsigned long p = *ppos;
    unsigned int count = size;
    int ret = 0;
    struct chardev_dev *dev = filp->private_data;
    
    if (p >= MAX_SIZE)
        return -EINVAL;
    if (count > MAX_SIZE - p)
        count = MAX_SIZE - p;
        
    if (copy_from_user(dev->mem + p, buf, count))                //从用户空间得到写入的数据
        ret = -EINVAL;
    else                    
    {
        *ppos += count;
        ret = count;
        printk(KERN_INFO "write %u byte(s) from %lu\n", count, p);
    }
    
    return ret;
}
 
 
static loff_t chardev_llseek(struct file *filp, loff_t offset, int orig)                     //llseek函数
{
    loff_t ret = 0;
    
    switch (orig)                                     //判断文件指针的位置,确定从何开始读写
    {
    case 0:
        if (offset < 0)
        {
            ret = -EINVAL;
            break;
        }
        if (offset > MAX_SIZE)
        {
            ret = -EINVAL;
            break;
        }
        filp->f_pos = offset;
        ret = filp->f_pos;
        break;
        
    case 1:
        if ((filp->f_pos + offset) < 0 )
        {
            ret = -EINVAL;
            break;
        }
        
        if ((filp->f_pos + offset) > MAX_SIZE)
        {
            ret = -EINVAL;
            break;
        }
        filp->f_pos += offset;
        ret = filp->f_pos;
        break;
        
    default:
        ret = -EINVAL;
        break;
    }
    
    return ret;
}
 
 
static const struct file_operations chardev_fops =                  //字符设备操作函数定义
{
    .owner  =  THIS_MODULE,
    .llseek =  chardev_llseek,
    .read   =  chardev_read,
    .write  =  chardev_write,
    .unlocked_ioctl = chardev_ioctl,
    .open   =  chardev_open,
    .release = chardev_release,
};
 
static char *chardev_devnode(struct device *dev, umode_t *mode)
{
    if (mode)
        *mode = 0666;
 
    return NULL;
}
 
static int __init chardev_init(void)                              //初始化,入口函数
{
    int ret;
    int err;
    
    ret = alloc_chrdev_region(&devno, 0, 1, "chardev");            //动态申请设备号
    if (ret < 0)
        return ret;
        
    devp = kzalloc(sizeof(struct chardev_dev), GFP_KERNEL);         //分配内存空间
    if (!devp)
    {
        ret = -ENOMEM;
        goto fail_malloc;
    }
    
    class = class_create(NULL, "chardev");                          //创建类节点
    if (IS_ERR(class))
    {
        ret = PTR_ERR(class);
        printk(KERN_ERR "class create error %d\n", ret);
        goto fail_malloc;
    }
    
    class->devnode = chardev_devnode;                              //创建设备节点
    dev = device_create(class, NULL, devno, NULL, "chardev");
    if (IS_ERR(class))
    {
        ret = PTR_ERR(dev);
        printk(KERN_ERR "device create error %d\n", ret);
        goto bad_device_create;
    }
    
    
    cdev_init(&devp->cdev, &chardev_fops);                          //绑定操作函数的结构体
    devp->cdev.owner = THIS_MODULE;
    err = cdev_add(&devp->cdev, devno, 1);                          //调用cdev_add函数将cdev结构体注册到内核
    if (err)
        printk(KERN_NOTICE "Error %d adding chardev", err);
        
    return 0;
    
    
bad_device_create:
    class_destroy(class);
fail_malloc:
    unregister_chrdev_region(devno, 1);
    return ret;
}
 
 
static void __exit chardev_exit(void)                             //注销
{
    device_destroy(class, devno);
    class_destroy(class);
    cdev_del(&devp->cdev);
    kfree(devp);
    unregister_chrdev_region(devno, 1);
}
 
module_init(chardev_init);                                      //加载模块
module_exit(chardev_exit);                                      //退出模块
 
MODULE_AUTHOR("lql");
MODULE_LICENSE("GPL");

 驱动文件的makefile,注意这里是Makefile,不是makefile,文件首字母要大写。因为在执行make时系统自动找Makefile文件,而不是makefile文件。也许可以通过什么配置解决这个问题。

obj-m := char.o   #确定文件名
modules:
	$(MAKE) -C /lib/modules/5.13.0-30-generic/build M=/home/wang modules

clean:
	rm -rf *.o *~ core .depend .*.cmd *.ko *.mod.c .tmp_versions

 Makefile文件放在/home/wang目录下,char.c放在/lib/modules/5.13.0-30-generic/build目录下。

说明:不用纠结5.13.0-30,你的环境中在/lib/modules/下面只有一个叫***-generic/build的路径。

当前路径是/home/wang,敲命令:make

不出意外生成文件char.ko.

将该文件拷贝到/lib/modules/5.13.0-30-generic/。这里的5.13.0-30版本号和makefile中的版本号一致。  

进入上述的驱动模块路径: cd /lib/modules/5.13.0-30-generic/

depmod, 产生模块依赖的映射文件

insmod char.ko  加载此驱动

mknod /dev/chardev   创建/dev/chardev设备节点。

如果执行报错 可以试试mknod /dev/chardev c 50 1,或者mknod /dev/chardev c,其中c表示是字符驱动,50是主设备号,1是从设备号。因为本实例自动获取设备号,所以加不加无所谓。

然后ls /dev/  看看在dev路径下是否生成了chardev文件。

说明:mknod 执行一次创建设备节点就行了,以后卸载(rmmod char.ko)再加载(insmod),不用执行mknod ,加载时自动创建相同的设备节点。

2、写应用代码并测试

这个驱动文件就像是一个文件的fd一样,我们可以往里写数据再读数据。

char_write.c

#include<sys/types.h>
#include<unistd.h>
#include<sys/stat.h>
#include<stdio.h>
#include<fcntl.h>
#include<string.h>
int main(int argc, char *argv[])
{
    int fd;
    char msg[100];
    fd= open(argv[1],O_RDWR,S_IRUSR|S_IWUSR);
    if(fd!=-1)
    {
        while(1)
        {
            printf("Please input the globar:(input quit go out)\n");
            scanf("%s",msg);
            if(strcmp(msg,"quit")==0)
              {
                close(fd);
                break;
              }
            write(fd,msg,strlen(msg));
            
        }
    }
    else
    {
        printf("device open failure\n");
    }
    return 0;
}

编译:在源文件当前目录下执行:gcc char_write.c -o write.out

这里是纯系统环境,没有硬件。如果是硬件,可以使用arm的交叉编译器。

当前目录下会生成文件write.out

char_read.c

#include<sys/types.h>
#include<unistd.h>
#include<sys/stat.h>
#include<stdio.h>
#include<fcntl.h>
#include<string.h>
int main(int argc, char *argv[])
{
    int fd,i;
    char msg[101];
    fd= open(argv[1],O_RDWR,S_IRUSR|S_IWUSR);
    if(fd!=-1)
    {
            for(i=0;i<101;i++)
                msg[i]='\0';
            read(fd,msg,100);
            printf("%s\n",msg);
    }
    else
    {
        printf("device open failure,%d\n",fd);
    }
    return 0;
}

编译:在源文件当前目录下执行:gcc char_read.c -o read.out

测试:

当前目录敲  ./write.out /dev/chardev 运行写程序。往驱动文件中写一串字符进去,敲quit退出程序。

root@ubuntu:/home/wang# ./write.out  /dev/chardev
Please input the globar:(input quit go out)
123
Please input the globar:(input quit go out)
quit
root@ubuntu:/home/wang# 

然后敲 ./read.out /dev/chardev 打印输出。

root@ubuntu:/home/wang# ./read.out 
123
root@ubuntu:/home/wang# 

至此结束。

具体代码见个人百度网盘路径:我的网盘/驱动开发练习/简单的字符设备linux开发/

  • 1
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值