驱动开发(1)——字符设备驱动

一、驱动含义

        字符设备是Linux驱动中最基本的一类设备驱动,字符设备就是一个字节,按照字节进行读写操作设备,读写数据是分先后顺序的。比如我们常见的点灯、按键、IIC、SPI、LCD等都是字符设备,这些设备的驱动就叫做字符设备驱动。
       在Linux中开发一般只能是用户态,也就是用户只能编写应用程序,但是要作用于内核,那么就需要了解Linux中应用程序是如何调用内核中的驱动程序的,Linux 应用程序对驱动程序的调用如下图所示:     

驱动开发(1)——字符设备驱动_第1张图片

         在Linux 中一切皆为文件,驱动加载成功以后会在“/dev”目录下生成一个相应的文件,应用程序通过对这个名为“/dev/xxx” (xxx 是具体的驱动文件名字)的文件进行相应的操作即可实现对硬件的操作。

        应用程序运行在用户空间,而 Linux 驱动属于内核的一部分,因此驱动运行于内核空间。当我们在用户空间想要实现对内核的操作,比如使用 open 函数打开/dev/led 这个驱动,因为用户空间不能直接对内核进行操作,因此必须使用一个叫做“系统调用”的方法来实现从用户空间陷入到内核空间,这样才能实现对底层驱动的操作。 open、 close、 write 和 read 等这些函数是有 C 库提供的,在 Linux 系统中,系统调用作为 C 库的一部分。当我们调用 open 函数的时候流程如图所示:

在这里插入图片描述

即大致这样: 

 驱动开发(1)——字符设备驱动_第2张图片

 二、设备号

        设备号分为主设备号次设备号。在Linux环境下打开/dev

驱动开发(1)——字符设备驱动_第3张图片

 主设备号与次设备号的区别:

主设备号用来区分不同种类的设备,次设备号用来区分同一类型的多个设备。

驱动链表:管理所有设备的驱动,驱动插入链表的顺序由设备号进行检索

1.添加:编写完驱动程序后,加载到内核。

2.查找:调用驱动程序,实现用户空间open或者read。

当我们在用户态调用open函数的时候就会发生一个软中断从而进入内核态(sys_call),中断号为:0x80(表示产生了一个系统调用);

open-->sys_call-->sys_open-->drv_open;

三、编写驱动的几个步骤

1.设备名

        我们可以手动创建设备名。

sudo mknod +设备名字 +设备类型(c表示字符设备驱动) +主设备号+次设备号 
b : create a block (buffered) pecial file。 
c, u: create a character (unbuffered) special file。 
p: create a FIFO

        删除手动创建的设备名直接rm就好。

比如:

驱动开发(1)——字符设备驱动_第4张图片

2.设备号

3.设备驱动函数:操作寄存器来驱动I/O口。

 一个简单的字符设备驱动代码:

#include 
#include 
#include 
#include 
#include 
#include 
#include 

static struct class *pin4_class;
static struct device *pin4_class_dev;

static dev_t devno;				//设备号
static int major=231;			//主设备号
static int minor=0;				//次设备号
static char *module_name="pin4";//模块名

//led open
static int pin4_open(struct inode *inode,struct file *file)
{
	printk("pin4_open\n");//内核打印函数用printk;与printf类似
	return 0;
}
//led_write
static ssize_t pin4_write(struct file *file1,const char __user *buf,ssize_t count,loff_t *ppos)
{
	printk("pin4_write\n");
	return 0;
}
//led_read
static int pin4_read(struct file * file,char __user *buf,size_t size,loff_t *ppos)
{
	printk("pin4_read\n");
	return 0;
}

static struct file_operations pin4_fops={
	.owner=THIS_MODULE,
	.open=pin4_open,
	.write=pin4_write,
	.read=pin4_read,
};

int __init pin4_drv_init(void)
{
	int ret;
	devno=MKDEV(major,minor);		//创建设备号
	ret=register_chrdev(major,module_name,&pin4_fops);	//注册驱动,表示把告诉内核把这个驱动加载到
														//内核的链表里面

	pin4_class=class_create(THIS_MODULE,"myfirstdemo");//让代码在dev下自动生成设备--类
	pin4_class_dev=device_create(pin4_class,NULL,devno,NULL,module_name);//创建设备文件

	return 0;
}

void __exit pin4_drv_exit(void)
{
	device_destroy(pin4_class,devno);
	class_destroy(pin4_class);
	unregister_chrdev(major,module_name);
}

module_init(pin4_drv_init);//函数入口
module_exit(pin4_drv_exit);//销毁设备
MODULE_LICENSE("GPL v2");

这个字符驱动流程大概是这样的:

驱动开发(1)——字符设备驱动_第5张图片

四、驱动编译

1.将上面的驱动文件放进Ubuntu中

 2.修改Makefile

驱动开发(1)——字符设备驱动_第6张图片

 obj-m:表示以生成块的方式编译,最终生成.ok的文件

 3.然后运行编译命令:

ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- KERNEL=kernel7 make modules

驱动开发(1)——字符设备驱动_第7张图片

 编译成功!发现多了一些文件。

 4.将编译好的.ko文件拷贝到树莓派

scp pin4driver.ko [email protected]:/home/pi

 5.交叉编译我们的pin4test.c文件

#include 
#include 
#include 
#include 

void main()
{
        int fd,data;
        fd = open("/dev/pin4",O_RDWR);
        if(fd<0){
                printf("open fail\n");
                perror("reson:");
        }
        else{
                printf("open successful\n");
        }
        fd=write(fd,'1',1);
}

 驱动开发(1)——字符设备驱动_第8张图片

  拷贝成功:

 6.加载内核驱动.

sudo insmod pin4driver.ko

     

 查看一下:驱动开发(1)——字符设备驱动_第9张图片

 设备添加成功!

7.添加权限后运行运行(666:所有用户都可读写)

 打开成功但是没有任何打印;因为打印在内核态,而我们运行的终端是用户态,那我们怎么看到内核态的打印呢?

用dmesg查看:(打印内核的printk,将其检索出来)

驱动开发(1)——字符设备驱动_第10张图片

 验证成功!

如果需要卸载内核驱动的话:

sudo rmmod xxx(不需要加ko)

你可能感兴趣的:(驱动基础,linux)