设备号:包含两个部分:主设备号和次设备
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);
}
运行时可能会出现不阻塞,一直出现打印信息的情况,之后的博文中会说到