驱动程序与应用程序
- 驱动程序运行在内核层,应用程序运行在应用层
- 驱动程序没有main函数, 包含提供给驱动开发人员搭建驱动的统一框架module_init module_exit,提供给应用程序使用的功能函数(这些函数包含在struct file_operations, 这个结构体中有很多的函数指针,一个函数指针对应一个功能函数) 所以,驱动程序可以给应用程序屏蔽硬件细节
- 内核是c语言写的,60%的代码是驱动程序
- 应用程序的标准头文件在/usr/include文件夹下,内核的标准头文件在/usr/src/linux-headers-3.13.0-24-generic/include下
设备类型
字符设备 char device: 大部分驱动程序都是字符设备,以字符为单位进行输入输出 块设备 block device: 硬盘 内存等,以块(通常为4kb)为单位进行输入输出 网络设备 net device: 网卡
驱动程序相关的命令
- 编译命令make,生成内核驱动程序文件.ko
- 将驱动程序加载到内核: insmod xxx.ko
- 查看内核中的驱动程序: lsmod
- 删除内核驱动程序: rmmod xxx.ko
- 创建设备文件:mknod 文件名 c 主设备号 次设备号
Makefile: hello.o为驱动程序代码文件
ifeq ($(KERNELRELEASE),)
KERNELDIR ?= /lib/modules/$(shell uname -r)/build
PWD := $(shell pwd)
modules:
$(MAKE) -C $(KERNELDIR) M=$(PWD) modules
modules_install:
$(MAKE) -C $(KERNELDIR) M=$(PWD) modules_install
clean:
rm -rf *.o *~ core .depend .*.cmd *.ko *.mod.c .tmp_versions m* Mo*
.PHONY: modules modules_install clean
else
obj-m := hello.o
endif
第一个简单的驱动程序
hello.c
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/init.h>
// 开源协议声明
MODULE_LICENSE ("GPL");
static int __init hello_2_init (void)
{
printk (KERN_INFO "hello driver init.\n");
return 0;
}
static void __exit hello_2_exit (void)
{
printk (KERN_INFO "hello driver exit.\n");
}
module_init (hello_2_init);// module_init insmod时执行的功能函数
module_exit (hello_2_exit);// module_exit rmmod时执行的功能函数
make
insmod hello.ko
rmmod hello.ko
dmesg -T
字符设备驱动
描述字符设备的结构体cdev /usr/src/linux-headers-3.13.0-24-generic/include/linux/cdev.h
struct cdev {
struct kobject kobj;
struct module *owner;
const struct file_operations *ops; //open close read write函数
struct list_head list;
dev_t dev; //设备号 typedef unsigned int dev_t
unsigned int count;
};
设备号就是一个无符号的整数 unsigned int, 32bit 设备号由两部分组成: 主设备号(设备的类型)占用高12位、次设备号(同类型设备采用次设备号来区别)占用低20位
file_operations
- open
- close
- read
- write
#include "linux/kernel.h"
#include "linux/module.h"
#include "linux/init.h"
#include "linux/cdev.h"
#include "linux/kdev_t.h"
#include "linux/fs.h"
#include "asm/uaccess.h"
MODULE_LICENSE ("GPL");
struct cdev mydev;
dev_t dev;
//主设备号100, 次设备号1
int major = 100, minor = 1;
//内核缓冲区
char kernel_buf[128] = {0};
int myopen(struct inode *pnode,struct file *pfile)
{
printk(KERN_INFO "myopen function exec\n");
return 0;
}
int myclose(struct inode *pnode,struct file *pfile)
{
printk(KERN_INFO "myclose function exec\n");
return 0;
}
ssize_t mywrite (struct file *pfile, char __user *buf, size_t len, loff_t * plofft)
{
int ret;
if (len > 128)
{
len = 128;
}
printk(KERN_INFO "mywrite function exec, %s, len = %d\n", buf, len);
ret = copy_from_user(kernel_buf, buf, len);//返回还未拷贝的字节数
printk(KERN_INFO "ret = %d\n", ret);
return len - ret;//返回内核拷贝了多少字节数
}
ssize_t myread(struct file *pfile, char __user *buf, size_t len, loff_t * plofft)
{
int ret;
char buffer[] = "this is kernel data.";
ret = copy_to_user(buf, buffer, sizeof(buffer)-1);
printk(KERN_INFO "myread function exec, ret = %d\n", ret);
return sizeof(buffer)-1 - ret;
}
struct file_operations myops=
{
.open = myopen,
.release = myclose,
.write = mywrite,
.read = myread,
};
static int __init myinit(void)
{
int ret;
//( (major & 0xFFF) << 20 ) | minor & 0xFFFFF;
dev = MKDEV(major,minor);
//向内核申请检测设备号是否可以使用,第一个参数为设备号,第二个参数为设备个数,第三个参数为设备名称
//成功返回0,失败返回-1
ret = register_chrdev_region(dev,1,"hello");
if(ret < 0)
{
// 向内核申请动态分配设备号,dev接收内核动态分配设备号指针,第二个参数为次设备号
// 成功返回0,失败返回-1
ret = alloc_chrdev_region(&dev,1,1,"hello");
if (ret < 0)
{
printk(KERN_INFO "alloc dev id error\n");
return 0;
}
}
major = MAJOR(dev);
minor = MINOR(dev);
printk(KERN_INFO "hello.ko device major = %d,minor = %d\n",major,minor);
//初始化cdev结构体file_operations
cdev_init(&mydev,&myops);
// 初始化cdev结构体成员设备号dev,将struct dev对象加载到内核中, 等同于mydev.dev = dev_id;
cdev_add(&mydev,dev,1);
printk(KERN_INFO "myinit function exec success.\n");
return 0;
}
static void __exit myexit(void)
{
cdev_del(&mydev);
// 向内核注销设备号
unregister_chrdev_region(dev,1);
printk(KERN_INFO "myexit function exec\n");
return ;
}
module_init(myinit);
module_exit(myexit);
应用程序调用驱动函数
加载驱动程序,创建设备文件
make && insmod hello.ko && mknod /dev/hello c 100 1
写应用代码,调用驱动程序
#include "unistd.h"
#include "fcntl.h"
#include "stdio.h"
#include "stdlib.h"
#include "string.h"
int main()
{
int fd;
char buf[128]={0};
int len;
fd = open("/dev/hello",O_RDWR,0777);
if(fd < 0)
{
printf("open hello error\n");
_exit(-1);
}
//从内核驱动中读取数据到buffer
len = read(fd, buf, strlen(buf));
printf("len = %d, recv from kernel = %s\n", len, buf);
memset(buf, 0, 128);
while(1)
{
printf("please input write to kernel data\n");
scanf("%s", buf);
//将终端输入数据写入内核缓冲区
len = write(fd, buf, strlen(buf));
printf("len = %d\n", len);
memset(buf, 0, 128);
}
close(fd);
return 0;
}
应用程序与内核的交互
应用程序的write与内核的write函数的交互
ssize_t mywrite (struct file *pfile, char __user *buf, size_t len, loff_t * plofft)
-
内核的write函数的参数 第二个参数是应用程序的数据的首地址,第三个参数是应用程序的数据的长度(应用程序想写多少个字节数据到内核)
-
内核的write函数的返回值会发送给应用程序,即是应用write函数实际写的字节数
-
应用程序的存储空间与内核的地址是不一样的 应用层存储空间与内核层存储空间的相互复制函数 copy_from_user 将数据从应用程序空间复制到内核空间 copy_to_user 将数据从内核空间复制到应用程序空间
应用程序的read与内核的read函数的交互
ssize_t myread(struct file *pfile, char __user *buf, size_t len, loff_t * plofft)
- 非阻塞 内核的read函数是一个非阻塞方式
- 阻塞
内核的阻塞机制
内核的阻塞机制:当应用程序从内核读取数据时,若此时内核没有数据(硬件没有穿送数据给内核),则会让应用程序等待(睡眠),等有数据时会唤醒应用程序。 进程阻塞是通过链式队列来实现,这个队列称为等待队列。Linux内核提供了有关等待队列来实现的接口:
-
wait_queue_head_t my_queue 定义一个等待队列头,相当于链式队列的头结点
-
init_waitqueue_head(queue) 初始化队列头,相当于生成了一个空队列
-
DECLARE_WAITQUEUE(queue, tsk) 建立新节点,新节点的数据是应用程序的进程, 此时该节点还未放入等待队列 第一个参数是定义的等待队列变量,第二个参数是当前使用应用程序进程,一般使用current,current是一个默认的指针,代表的是当前用户进程
-
add_wait_queue(&queue,&node) 将新节点加入等待队列
-
wait_event_interruptible(queue, condition) 使等待队列中的进程睡眠,该睡眠可以被信号打断 第一个参数为等待队列头变量,第二个参数condition为睡眠条件,如果condition为假则一直睡眠,反之则退出睡眠 wait_event(queue, condition) 不可以被打断,kill -9也无法结束
-
wake_up_interruptible(&queue) 唤醒可被信号打断的等待队列上的睡眠进程
#include "linux/kernel.h"
#include "linux/module.h"
#include "linux/init.h"
#include "linux/cdev.h"
#include "linux/kdev_t.h"
#include "linux/fs.h"
#include "asm/uaccess.h"
#include "linux/sched.h"
MODULE_LICENSE ("GPL");
struct cdev mydev;
dev_t dev;
//主设备号100, 次设备号1
int major = 100, minor = 1;
//内核缓冲区
char kernel_buf[128] = {0};
//定义等待队列头结点
wait_queue_head_t my_queue;
//定义等待队列新节点
wait_queue_t new_node;
int flag = 1;
int myopen(struct inode *pnode,struct file *pfile)
{
printk(KERN_INFO "myopen function exec\n");
return 0;
}
int myclose(struct inode *pnode,struct file *pfile)
{
printk(KERN_INFO "myclose function exec\n");
return 0;
}
ssize_t mywrite (struct file *pfile, char __user *buf, size_t len, loff_t * plofft)
{
flag = 0;
wake_up_interruptible(&my_queue);
return 0;
}
ssize_t myread(struct file *pfile, char __user *buf, size_t len, loff_t * plofft)
{
printk(KERN_INFO "myread function exec\n");
DECLARE_WAITQUEUE(new_node, current);
add_wait_queue(&my_queue, &new_node);
wait_event_interruptible(my_queue, flag != 1);
return 0;
}
struct file_operations myops=
{
.open = myopen,
.release = myclose,
.write = mywrite,
.read = myread,
};
static int __init myinit(void)
{
int ret;
//( (major & 0xFFF) << 20 ) | minor & 0xFFFFF;
dev = MKDEV(major,minor);
//向内核申请检测设备号是否可以使用,第一个参数为设备号,第二个参数为设备个数,第三个参数为设备名称
//成功返回0,失败返回-1
ret = register_chrdev_region(dev,1,"hello");
if(ret < 0)
{
// 向内核申请动态分配设备号,dev接收内核动态分配设备号指针,第二个参数为次设备号
// 成功返回0,失败返回-1
ret = alloc_chrdev_region(&dev,1,1,"hello");
if (ret < 0)
{
printk(KERN_INFO "alloc dev id error\n");
return 0;
}
}
major = MAJOR(dev);
minor = MINOR(dev);
printk(KERN_INFO "hello.ko device major = %d,minor = %d\n",major,minor);
//初始化cdev结构体file_operations
cdev_init(&mydev,&myops);
// 初始化cdev结构体成员设备号dev,将struct dev对象加载到内核中, 等同于mydev.dev = dev_id;
cdev_add(&mydev,dev,1);
// 初始化等待队列头,相当于生成了一个空队列
init_waitqueue_head(&my_queue);
printk(KERN_INFO "myinit function exec success.\n");
return 0;
}
static void __exit myexit(void)
{
cdev_del(&mydev);
// 向内核注销设备号
unregister_chrdev_region(dev,1);
printk(KERN_INFO "myexit function exec\n");
return ;
}
module_init(myinit);
module_exit(myexit);
内核的并发处理
同一个驱动程序,可以同时被多个应用程序所使用,若不做处理,则有可能会使得驱动程序数据无序 怎样解决这个问题: 在一段时间内,一个驱动程序只为一个应用程序服务 可以类似于多线程中的锁机制,此时的共有资源就是驱动程序
- 原子变量 驱动开发人员不能直接操作原子变量,必须使用内核提供的相关函数接口去操作它:
atomic_t v;
atomic_set(atomic_t *v, int value); //*v = value
atomic_inc(atomic_t *v) // (*v)++
atomic_dec(atomic_t *v) //(*v)--
atomic_dec_test(atomic_t *v) //原子变量减1操作后测试其是否为0,若为0则返回非0(真),否则返回0(假)
原子变量的使用模板:
static atomic_t xxx_available = ATOMIC_INIT(1); // 定义原子变量
static init xxx_open(struct inode *inode, struct file *flip)
{
if (!atomic_dec_and_test(&xxx_available))
{
atomic_inc(&xxx_available);
return -EBUSY; // 已经打开
}
return 0;
}
static init xxx_release(struct inode *inode, struct file *flip)
{
atomic_inc(&xxx_available);//释放设备
return 0;
}
#include "linux/kernel.h"
#include "linux/module.h"
#include "linux/init.h"
#include "linux/cdev.h"
#include "linux/kdev_t.h"
#include "linux/fs.h"
#include "asm/uaccess.h"
MODULE_LICENSE ("GPL");
struct cdev mydev;
dev_t dev;
//主设备号100, 次设备号1
int major = 100, minor = 1;
//内核缓冲区
char kernel_buf[128] = {0};
atomic_t v;
int myopen(struct inode *pnode,struct file *pfile)
{
if (!atomic_dec_and_test(&v))
{
atomic_inc(&v);
return -1;
}
printk(KERN_INFO "myopen function exec\n");
return 0;
}
int myclose(struct inode *pnode,struct file *pfile)
{
atomic_inc(&v);
printk(KERN_INFO "myclose function exec\n");
return 0;
}
ssize_t mywrite (struct file *pfile, char __user *buf, size_t len, loff_t * plofft)
{
int ret;
if (len > 128)
{
len = 128;
}
printk(KERN_INFO "mywrite function exec, %s, len = %d\n", buf, len);
ret = copy_from_user(kernel_buf, buf, len);//返回还未拷贝的字节数
printk(KERN_INFO "ret = %d\n", ret);
return len - ret;//返回内核拷贝了多少字节数
}
ssize_t myread(struct file *pfile, char __user *buf, size_t len, loff_t * plofft)
{
int ret;
char buffer[] = "this is kernel data.";
ret = copy_to_user(buf, buffer, sizeof(buffer)-1);
printk(KERN_INFO "myread function exec, ret = %d\n", ret);
return sizeof(buffer)-1 - ret;
}
struct file_operations myops=
{
.open = myopen,
.release = myclose,
.write = mywrite,
.read = myread,
};
static int __init myinit(void)
{
int ret;
//( (major & 0xFFF) << 20 ) | minor & 0xFFFFF;
dev = MKDEV(major,minor);
//向内核申请检测设备号是否可以使用,第一个参数为设备号,第二个参数为设备个数,第三个参数为设备名称
//成功返回0,失败返回-1
ret = register_chrdev_region(dev,1,"hello");
if(ret < 0)
{
// 向内核申请动态分配设备号,dev接收内核动态分配设备号指针,第二个参数为次设备号
// 成功返回0,失败返回-1
ret = alloc_chrdev_region(&dev,1,1,"hello");
if (ret < 0)
{
printk(KERN_INFO "alloc dev id error\n");
return 0;
}
}
major = MAJOR(dev);
minor = MINOR(dev);
printk(KERN_INFO "hello.ko device major = %d,minor = %d\n",major,minor);
//初始化cdev结构体file_operations
cdev_init(&mydev,&myops);
// 初始化cdev结构体成员设备号dev,将struct dev对象加载到内核中, 等同于mydev.dev = dev_id;
cdev_add(&mydev,dev,1);
atomic_set(&v, 1);
printk(KERN_INFO "myinit function exec success.\n");
return 0;
}
static void __exit myexit(void)
{
cdev_del(&mydev);
// 向内核注销设备号
unregister_chrdev_region(dev,1);
printk(KERN_INFO "myexit function exec\n");
return ;
}
module_init(myinit);
module_exit(myexit);