linux驱动学习笔记---如何写一个简单的驱动程序(二)

设备号:包含两个部分:主设备号和次设备
crw--w----  1 root tty       4,  10 Jul 26 06:05 tty10
crw--w----  1 root tty       4,  11 Jul 26 06:05 tty11
crw--w----  1 root tty       4,  12 Jul 26 06:05 tty12


主设备号(整数) : 表示某一类设备

次设备号(整数):某一类设备中的某一个

设备号:32bit整数===> 高12bit(主设备号) | 低20bit次设备号

设备号类型: dev_t
        #define MKDEV(ma,mi)    (((ma) << MINORBITS) | (mi)) //得到设备号
        
    如果有设备号,得到主设备号和次设备号
        #define MAJOR(dev)    ((unsigned int) ((dev) >> MINORBITS)) //根据设备号得到主设备号
        #define MINOR(dev)    ((unsigned int) ((dev) & MINORMASK)) //根据设备号得到次设备号


================================================================================
创建设备节点/设备文件
两种方法:
    1, 手动创建
        mknod   设备文件名字(自定义)  类型  主设备号(和驱动中的主设备号保持一致)  次设备号
        
        mknod /dev/myhello  c  256  0
        
        [root@farsight /drv_module]# ls /dev/myhello  -l
        crw-r--r--    1 0        0         265,   0 Jan  1 00:00 /dev/myhello

        注意: 
            /dev目录下的文件都是在内存中,掉电就消失
    
    
    
    2, 代码自动创建(驱动加载进入之后,同时帮我们创建设备节点)
        设备是有分类:类的名字, 设备是隶属于类别
        // 自动创建设备节点
        //创建设备文件所属类别
        //参数1--拥有者--当前模块
        //参数2--类别的名字--自定义
        //返回值---返回一个指针
        hello_cls = class_create(THIS_MODULE, "hello_cls");

        //创建设备文件
        //参数1--所属类别
        //参数2--当前创建的设备文件的父类是谁--一般NULL
        //参数3--关联的设备号
        //参数4--当前设备文件的私有数据--一般NULL
        //参数5/6--设定设备文件的名字
        device_create(hello_cls, NULL, MKDEV(dev_major, 0), NULL, "myhello_dev%d", 0); // /dev/myhello_dev0
    
    
    相反的释放资源的方法:
    //参数1--所属类别
    //参数2--关联的设备号
    device_destroy(hello_cls, MKDEV(dev_major, 0));

    //参数1--所属类别
    class_destroy(hello_cls);


=====================================================================
应用程序如何与驱动进行交互:


    open()           read()            write()          close()
------------------------------------------------------------------------------
驱动:
    xxx_open()       xxx_read()         xxx_write()      xxx_close()
    {
        
        
        
    }

--------------------------------
硬件

=====================================================
驱动中操作硬件必须将物理地址转换成虚拟地址

    //参数1--硬件的物理地址
    //参数2--映射的地址长度
    //返回值---映射之后的虚拟地址
    gpc0con = ioremap(0xE0200060,  8);

    //去映射
    //参数1---映射之后的虚拟地址
    iounmap(gpc0con);
    
===========================================================
用户空间和内核空间之间的数据的交互

    //从用户空间拷贝数据到内核空间--一般都是在驱动代码中的xxx_write()中使用
    unsigned long copy_from_user(void * to, const void __user * from, unsigned long n)
    
    //从内核空间拷贝数据到用户空间--一般都是在驱动代码中的xxx_read()中使用
    unsigned long copy_to_user(void __user * to, const void * from, unsigned long n)

这两个方法会对检验传入进来的指针是否为空指针


一.如何写一个驱动程序

1.添加头文件

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

 2,声明驱动模块加载/卸载入口函数

module_init(hello_drv_init);
module_exit(hello_drv_exit);

 3,实现驱动模块加载/卸载入口函数

卸载函数应该与安装函数顺序刚好相反

static int hello_drv_init(void)
{
	printk("--------^_*  %s-------\n", __FUNCTION__);
	int ret;

	// 申请主设备号, 默认次设备号为0
	// 参数1---指定的主设备号--就是一个整数,选255以上
	//参数2--设备的描述--自定义的字符串
	//参数3--设备驱动的文件操作对象
	//返回值: 错误为负数,正确为0
	ret = register_chrdev(dev_major, "hello_device",  &hello_fops);
	if(ret < 0)
	{
		printk("register_chrdev error\n");
		return ret;
	}

	// 自动创建设备节点
	//创建设备文件所属类别
	//参数1--拥有者--当前模块
	//参数2--类别的名字--自定义
	//返回值---返回一个指针
	hello_cls = class_create(THIS_MODULE, "hello_cls");

	//创建设备文件
	//参数1--所属类别
	//参数2--当前创建的设备文件的父类是谁--一般NULL
	//参数3--关联的设备号
	//参数4--当前设备文件的私有数据--一般NULL
	//参数5/6--设定设备文件的名字
	device_create(hello_cls, NULL, MKDEV(dev_major, 0), NULL, "myhello_dev%d", 0); // /dev/myhello_dev0
	
	return 0;
}

static void hello_drv_exit(void)
{

	printk("--------^_*  %s-------\n", __FUNCTION__);
	//参数1--所属类别
	//参数2--关联的设备号
	device_destroy(hello_cls, MKDEV(dev_major, 0));
	class_destroy(hello_cls);
	
	// 参数1---指定的主设备号--就是一个整数,选255以上
	//参数2--设备的描述--自定义的字符串
	unregister_chrdev(dev_major, "hello_device");

}

4, 添加gpl认证

MODULE_LICENSE("GPL");

 二.实现应用文件调用IO驱动

在设备号申请时会有一个fops,上文中我们没有实现.

文件操作对象---为用户程序提供文件io接口的对象

const struct file_operations hello_fops = {
	.owner = THIS_MODULE,
	.open = hello_drv_open,
	.read = hello_drv_read,
	.write = hello_drv_write,
	.release = hello_drv_close,
};

实现结构体中函数指针所指向的函数

int hello_drv_open (struct inode *inode, struct file *filp)
{
	printk("--------^_*  %s-------\n", __FUNCTION__);

	return 0;
}

//  read(fd, buf, size);
ssize_t hello_drv_read(struct file *filp, char __user *buf,  size_t count, loff_t *fpos)
{
	printk("--------^_*  %s-------\n", __FUNCTION__);
	return 0;
}


ssize_t hello_drv_write(struct file *filp, const char __user *buf, size_t count, loff_t *fpos)
{
	printk("--------^_*  %s-------\n", __FUNCTION__);
	return 0;
}


int hello_drv_close(struct inode *inode, struct file *filp)
{
	printk("--------^_*  %s-------\n", __FUNCTION__);
	return 0;
}

另外写一个应用层app程序,调用驱动


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





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

	char buf[32];
	
	//直接将驱动模块当做文件来操作
	int fd = open("/dev/myhello_dev0", O_RDWR);
	if(fd < 0)
	{
		perror("open");
		exit(1);
	}

	write(fd, buf, 32);

	read(fd, buf, 32);

	

	close(fd);


}

运行时可能会出现不阻塞,一直出现打印信息的情况,之后的博文中会说到

猜你喜欢

转载自blog.csdn.net/weixin_42471952/article/details/81586294