《linux驱动:字符设备驱动之一》

目录

前言

框架

常用数据结构

常用函数

button 字符设备驱动

编译

        编译进内核

        编译成单独模块

测试

小结


前言

LINUX 驱动针对的对象是存储器和外设,而不是针对cpu内核。存储器和外设分为3个基础大类:

  •   字符设备     
  •   块设备     
  •   网络设备

        字符设备指那些必须以串行顺序依次进行访问的设备,如触摸屏、鼠标等。块设备可以按任意顺序进行访问,以块为单位进行操作,如硬盘、eMMC等。字符设备和块设备的驱动设计有很大的差异,但对于用户(应用程序)而言,它们都使用文件系统的接口(open()、write()、read()、close())来进行访问和操作。
        网络设备面向数据的接收和发送设计,它不对应文件系统的节点。内核和网络设备的通信与内核和字符设备、块设备的通信方式完全不同,前者主要还是使用套接字接口。

框架

常用数据结构

include\linux\device.h

        一个 struct class 结构体类型变量对应一个类,内核提供了class_create() 函数,可以用它来创建一个类,这个类存放于 sysfs 下面。 一旦创建了类,再用class_device_create() 函数就可以在 /dev 目录下创建相应的设备节点。

struct class {
	const char		* name;   //类名称
	struct module		* owner; //类所属的模块,比如led模块
	struct kset		subsys;
	struct list_head	children;
	struct list_head	devices;
	struct list_head	interfaces;
	struct kset		class_dirs;
	struct semaphore	sem;	/* locks both the children and interfaces lists */

	struct class_attribute		* class_attrs; // 类所添加的属性
	struct class_device_attribute	* class_dev_attrs;
	struct device_attribute		* dev_attrs;

	int	(*uevent)(struct class_device *dev, char **envp,
			   int num_envp, char *buffer, int buffer_size);
	int	(*dev_uevent)(struct device *dev, char **envp, int num_envp,
				char *buffer, int buffer_size);

	void	(*release)(struct class_device *dev);  
	void	(*class_release)(struct class *class);  // 类被释放时调用的函数
	void	(*dev_release)(struct device *dev);    // 设备被释放时调用的函数


	int	(*suspend)(struct device *, pm_message_t state);
	int	(*resume)(struct device *);
};

  一个class可以看成是一个容器,包含了很多的class_device,这些class_device是由class这个大的容器来管理的,而每个class_device都对应着一个具体的设备。每个class对象包括一个class_device链表,每个class_device对象表示一个逻辑设备并通过struct class_device中的dev成员(一个指向struct device的指针)关联一个物理设备。一个逻辑设备总是对应一个物理设备,而一个物理设备却可以对应多个逻辑设备。 

struct class_device {
	struct list_head	node;

	struct kobject		kobj;
	struct class		* class;	/* required */
	dev_t			devt;		/* dev_t, creates the sysfs "dev" */
	struct class_device_attribute *devt_attr;
	struct class_device_attribute uevent_attr;
	struct device		* dev;		/* not necessary, but nice to have */
	void			* class_data;	/* class-specific data */
	struct class_device	*parent;	/* parent of this child device, if there is one */
	struct attribute_group  ** groups;	/* optional groups */

	void	(*release)(struct class_device *dev);
	int	(*uevent)(struct class_device *dev, char **envp,
			   int num_envp, char *buffer, int buffer_size);
	char	class_id[BUS_ID_SIZE];	/* unique to this class */
};

include\linux\fs.h

        file_operation就是把系统调用和驱动程序关联起来的关键数据结构。这个结构的每一个成员都对应着一个系统调用。用户进程利用系统调用在对设备进行读写操作时,系统调用通过设备文件的主设备号找到对应设备驱动程序,然后读取这个数据结构相应的函数指针,将控制权给到该函数。

struct file_operations {
	struct module *owner;
	loff_t (*llseek) (struct file *, loff_t, int);
	ssize_t (*read) (struct file *, char __user *, size_t, loff_t *);
	ssize_t (*write) (struct file *, const char __user *, size_t, loff_t *);
	ssize_t (*aio_read) (struct kiocb *, const struct iovec *, unsigned long, loff_t);
	ssize_t (*aio_write) (struct kiocb *, const struct iovec *, unsigned long, loff_t);
	int (*readdir) (struct file *, void *, filldir_t);
	unsigned int (*poll) (struct file *, struct poll_table_struct *);
	int (*ioctl) (struct inode *, struct file *, unsigned int, unsigned long);
	long (*unlocked_ioctl) (struct file *, unsigned int, unsigned long);
	long (*compat_ioctl) (struct file *, unsigned int, unsigned long);
	int (*mmap) (struct file *, struct vm_area_struct *);
	int (*open) (struct inode *, struct file *);
	int (*flush) (struct file *, fl_owner_t id);
	int (*release) (struct inode *, struct file *);
	int (*fsync) (struct file *, struct dentry *, int datasync);
	int (*aio_fsync) (struct kiocb *, int datasync);
	int (*fasync) (int, struct file *, int);
	int (*lock) (struct file *, int, struct file_lock *);
	ssize_t (*sendfile) (struct file *, loff_t *, size_t, read_actor_t, void *);
	ssize_t (*sendpage) (struct file *, struct page *, int, size_t, loff_t *, int);
	unsigned long (*get_unmapped_area)(struct file *, unsigned long, unsigned long, unsigned long, unsigned long);
	int (*check_flags)(int);
	int (*dir_notify)(struct file *filp, unsigned long arg);
	int (*flock) (struct file *, int, struct file_lock *);
	ssize_t (*splice_write)(struct pipe_inode_info *, struct file *, loff_t *, size_t, unsigned int);
	ssize_t (*splice_read)(struct file *, loff_t *, struct pipe_inode_info *, size_t, unsigned int);
};

常用函数

    

// linux-2.6.22.6/fs/char_dev.c

/* register_chrdev() - 注册字符设备.
 * @major: 主设备号
 * @name: 设备名称
 * @fops: file_operations结构体

 如果major传入0,系统自动分配主设备号,返回值即为主设备号;
 如果majos传入>0,返回0为成功,返回值<0表示失败
*/

int register_chrdev(unsigned int major, const char *name,const struct file_operations *fops)


/* unregister_chrdev() - 卸载字符设备.
 * @major: 主设备号
 * @name: 设备名称
*/
int unregister_chrdev(unsigned int major, const char *name)
//linux-2.6.22.6/drivers/base/class.c

/**
 * class_create - 创建一个设备类
 * @owner: THIS_MODULE
 * @name: 设备类名称.
 *
 *
    创建成功返回 struct class 
 */
struct class *class_create(struct module *owner, const char *name)

/**
 * class_create - 销毁一个设备类
 * @cls: struct class
 *
    创建成功返回 struct class 
 */
void class_destroy(struct class *cls)
/**
 * class_device_create - creates a class device and registers it with sysfs
 * @cls: pointer to the struct class that this device should be registered to.
 * @parent: pointer to the parent struct class_device of this new device, if any.
 * @devt: the dev_t for the char device to be added.
 * @device: a pointer to a struct device that is assiociated with this class device.
 * @fmt: string for the class device's name
 *
 * This function can be used by char device classes.  A struct
 * class_device will be created in sysfs, registered to the specified
 * class.
 * A "dev" file will be created, showing the dev_t for the device, if
 * the dev_t is not 0,0.
 * If a pointer to a parent struct class_device is passed in, the newly
 * created struct class_device will be a child of that device in sysfs.
 * The pointer to the struct class_device will be returned from the
 * call.  Any further sysfs files that might be required can be created
 * using this pointer.
 *
 * Note: the struct class passed to this function must have previously
 * been created with a call to class_create().
 */
struct class_device *class_device_create(struct class *cls,
					 struct class_device *parent,
					 dev_t devt,
					 struct device *device,
					 const char *fmt, ...)


void class_device_unregister(struct class_device *class_dev)
// 从内核空间拷贝一块儿数据到用户空间
/*
   to 目标地址,这个地址是用户空间的地址;

   from 源地址,这个地址是内核空间的地址;

   n 将要拷贝的数据的字节数。

*/

int copy_to_user(void __user *to, const void *from, int n)


    module_init  修饰入口函数
    module_exit 修饰 出口函数
    MODULE_LICENSE 修饰 模块license

button 字符设备驱动

#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/fs.h>
#include <linux/init.h>
#include <linux/delay.h>
#include <asm/uaccess.h>
#include <asm/irq.h>
#include <asm/io.h>
#include <asm/arch/regs-gpio.h>
#include <asm/hardware.h>

#define DEVICE_NAME     "button"  /* 加载模块后,执行”cat /proc/devices”命令看到的主设备名称 */
#define BUTTON_MAJOR     232     /* 主设备号 */

static struct class *buttons_class;    //设备类  使用cd /sys/class 查看当前系统上的设备类
static struct class_device	*buttons_class_dev; //类设备,一个设备类下可以有多个类设备   ls /dev/* 查看当前系统上的设备。或者 cd /sys/class/类名/ 查看当前设备类下的设备。


/* 应用程序对设备文件/dev/button 执行open(...)时,
 * 就会调用s3c24xx_buttons_open函数
 */
static int s3c24xx_buttons_open(struct inode *inode, struct file *file)
{
	s3c2410_gpio_cfgpin(S3C2410_GPF0, S3C2410_GPF0_INP);
    s3c2410_gpio_cfgpin(S3C2410_GPF2, S3C2410_GPF2_INP);
    s3c2410_gpio_cfgpin(S3C2410_GPG3, S3C2410_GPG3_INP);

    return 0;
}



static int s3c24xx_buttons_read(struct file *filp, char __user *buff, size_t count, loff_t *offp)
{
    char buttons_status[3];

    buttons_status[0] = s3c2410_gpio_getpin(S3C2410_GPF0) & (0x1<<0)? 1: 0;
    buttons_status[1] = s3c2410_gpio_getpin(S3C2410_GPF2) & (0x1<<2)? 1: 0;
    buttons_status[2] = s3c2410_gpio_getpin(S3C2410_GPG3) & (0x1<<3)? 1: 0;


    copy_to_user(buff, buttons_status, sizeof(buttons_status));

    return 0;
}




static ssize_t s3c24xx_buttons_write(struct file *file, const char __user *buf, size_t count, loff_t * ppos)
{
    return 0;
}



/* 这个结构是字符设备驱动程序的核心
 * 当应用程序操作设备文件时所调用的open、read、write等函数,
 * 最终会调用这个结构中指定的对应函数
 */
static struct file_operations s3c24xx_buttons_fops = {
    .owner  =   THIS_MODULE,    /* 这是一个宏,推向编译模块时自动创建的__this_module变量 */
    .open   =   s3c24xx_buttons_open,     
	.read	=	s3c24xx_buttons_read,	   
	.write	=	s3c24xx_buttons_write,	   
};

/*
 * 执行insmod命令时就会调用这个函数 
 */
static int __init s3c24xx_button_init(void)
{
    int ret;

    /* 注册字符设备
     * 参数为主设备号、设备名字、file_operations结构;
     * 这样,主设备号就和具体的file_operations结构联系起来了,
     * 操作主设备为BUTTON_MAJOR的设备文件时,就会调用s3c24xx_buttons_fops中的相关成员函数
     * BUTTON_MAJOR可以设为0,表示由内核自动分配主设备号,返回值为主设备号
     */
    ret = register_chrdev(BUTTON_MAJOR, DEVICE_NAME, &s3c24xx_buttons_fops);
    if (ret < 0) {
      printk(DEVICE_NAME " can't register major number\n");
      return ret;
    }

    // 创建设备类
	buttons_class = class_create(THIS_MODULE, "buttons");
	if (IS_ERR(buttons_class))
		return PTR_ERR(buttons_class);
	
	// 创建类设备节点
    buttons_class_dev = class_device_create(buttons_class, NULL, MKDEV(BUTTON_MAJOR,0), NULL, "button");
    if (unlikely(IS_ERR(buttons_class_dev)))
        return PTR_ERR(buttons_class_dev);
	
    printk(DEVICE_NAME " initialized\n");
    return 0;
}

/*
 * 执行rmmod命令时就会调用这个函数 
 */
static void __exit s3c24xx_button_exit(void)
{
    printk("to unregister_chrdev \n");
	unregister_chrdev(BUTTON_MAJOR, DEVICE_NAME);
    printk("to class_device_unregister \n");
    class_device_unregister(buttons_class_dev);
    printk("to class_destroy \n");
    class_destroy(buttons_class);
}

/* 这两行指定驱动程序的初始化函数和卸载函数 */
module_init(s3c24xx_button_init);
module_exit(s3c24xx_button_exit);

/* 描述驱动程序的一些信息,不是必须的 */
MODULE_VERSION("0.1.0");
MODULE_DESCRIPTION("S3C2410/S3C2440 BUTTON Driver");
MODULE_LICENSE("GPL");

编译

        编译进内核

KERN_DIR = /home/book/linuxSrc/linux-2.6.22.6

all:
	make -C $(KERN_DIR) M=`pwd` modules 

clean:
	make -C $(KERN_DIR) M=`pwd` modules clean
	rm -rf modules.order

obj-y	+= buttons.o


        编译成单独模块

KERN_DIR = /home/book/linuxSrc/linux-2.6.22.6

all:
	make -C $(KERN_DIR) M=`pwd` modules 

clean:
	make -C $(KERN_DIR) M=`pwd` modules clean
	rm -rf modules.order

obj-m	+= buttons.o

测试

insmod buttons.ko 手动加载模块

rmmod buttons 卸载模块

lsmod 列出当前已加载的模块

 编译以下应用程序 arm-linux-gcc -o button_test buttons_test.c

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

int main(int argc, char **argv)
{
    int i;
    int ret;
    int fd;
    int press[3];
    
    fd = open("/dev/button", 0);  // 打开设备
    if (fd < 0) {
        printf("Can't open /dev/button\n");
        return -1;
    }

 
    while (1) {
        ret = read(fd, press, sizeof(press));
        if (ret < 0) {
            printf("read err!\n");
            continue;
        } 
        sleep(5);

        printf(" read buttons status : 0(%d)--2(%d)--11(%d) \n",press[0],press[1],press[2]);
    }
    
    close(fd);
    return 0;    
}


小结

        字符设备驱动的一般流程为

  • 构建file_operations结构体
static struct file_operations s3c24xx_buttons_fops = {
    .owner  =   THIS_MODULE,    /* 这是一个宏,推向编译模块时自动创建的__this_module变量 */
    .open   =   s3c24xx_buttons_open,     
	.read	=	s3c24xx_buttons_read,	   
	.write	=	s3c24xx_buttons_write,	   
};
  • 注册字符设备
    int ret;

    /* 注册字符设备
     * 参数为主设备号、设备名字、file_operations结构;
     * 这样,主设备号就和具体的file_operations结构联系起来了,
     * 操作主设备为BUTTON_MAJOR的设备文件时,就会调用s3c24xx_buttons_fops中的相关成员函数
     * BUTTON_MAJOR可以设为0,表示由内核自动分配主设备号,返回值为主设备号
     */
    ret = register_chrdev(BUTTON_MAJOR, DEVICE_NAME, &s3c24xx_buttons_fops);
    if (ret < 0) {
      printk(DEVICE_NAME " can't register major number\n");
      return ret;
    }
  • 创建类
// 创建设备类
	buttons_class = class_create(THIS_MODULE, "buttons");
	if (IS_ERR(buttons_class))
		return PTR_ERR(buttons_class);
  • 创建类设备节点
// 创建类设备节点
    buttons_class_dev = class_device_create(buttons_class, NULL, MKDEV(BUTTON_MAJOR,0), NULL, "button");
    if (unlikely(IS_ERR(buttons_class_dev)))
        return PTR_ERR(buttons_class_dev);
	
    printk(DEVICE_NAME " initialized\n");
    return 0;
  • 修饰驱动初始化和卸载接口
    module_init(s3c24xx_button_init);
    module_exit(s3c24xx_button_exit);

    另外需要加 MODULE_LICENSE("GPL");否则insmod加载模块时可能会提示“module license 'unspecified' taints kernel”

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

编程界的小学生、

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值