1.linux驱动和模块加载函数
Linux驱动有两种运行方式
第一种是将驱动编译Linux内核中,这样当Linux内核启动的时候就会自动运行驱动程序。
第二种是将驱动编译成模块(Linux下模块扩展名为".ko"),在Linux内核启动后使用命令"insmod"或者"modprobe"命令加载驱动模块。在调式驱动的时候一般都选择将其编译为模块,这样我门就不需要重新烧写Linux内核,非常方便。将驱动编译成模块是最方便开发的。
先分析加载的模块的依赖关系,然后都会将所有依赖的模块都加载到内核中
module_init(xxx_init); //注册模块加载函数
module_exit(xxx_exit); //注册模块卸载函数
MODULE_LICENSE() //添加模块 LICENSE 信息
MODULE_AUTHOR() //添加模块作者信息
/****************************************************
ICENSE: 许可证,我们使用GPL协议。GPL协议一般指GNU通用公共许可证。
GNU通用公共许可证简称为GPL,是由自由软件基金会发行的用于计算机软件
的协议证书,使用该证书的软件被称为自由软件。
*****************************************************/
======================================
2.设备号
在Linux中,为了方便管理,每个设备都有一个设备号,设备号由主设备号和次设备号组成。主设备号表示某一个具体的驱动,次设备号表示使用这个驱动的各个设备。Linux提供一个dev_t的数据类型表示设备号,在include/linux/types.h里面。
typedef __kernel_dev_t dev_t;
typedef __u32 __kernel_dev_t;
typedef unsigned int __u32;
可以看出dev_t其实就是unsigned int类型,无符号32位 。其中高12位表示主设备号,低20位表示次设备号。所以主设备号范围0~4095。
=======================================
3.注册字符设备函数
我们需要向系统注册一个字符设备,使用函数register_chrdev 。卸载驱动的时候需要注销掉前面注册的字符设备,使用函数unregister_chrdev,注销字符设备。
#include <linux/fs.h>
/* 注册字符设备函数 */
static inline int register_chrdev(unsigned int major,
const char *name,const struct file_operations *fops)
{
return __register_chrdev(major, 0, 256, name, fops);
}
/* 销毁字符设备函数 */
static inline void unregister_chrdev(unsigned int major, const char *name)
{
__unregister_chrdev(major, 0, 256, name);
}
/*****************************************************
参数说明:
major :主设备号。Linux下每一个设备都会有设备号,设备号分为主设备号和次设备号。后面有详细的讲。
name :设备的名字。指向遗传字符串。
fops :它时file_operations结构体的指针, file_operations前面我们也说过,就是 Linux 内核驱动操作
函数集合。它指向设备的操作函数集合变量。
******************************************************/
设备号的获取
从dev_t获取主设备号和次设备号,MAJOR(dev_t),MINOR(dev_t)。也可以使用主设备号和次设备号构成dev_t,通过MKDEV(major,minor);
file_operations 函数操作集合
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 (*read_iter) (struct kiocb *, struct iov_iter *);
ssize_t (*write_iter) (struct kiocb *, struct iov_iter *);
int (*iterate) (struct file *, struct dir_context *);
unsigned int (*poll) (struct file *, struct poll_table_struct *);
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 (*mremap)(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 *, loff_t, loff_t, 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 (*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 (*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);
int (*setlease)(struct file *, long, struct file_lock **, void **);
long (*fallocate)(struct file *file, int mode, loff_t offset,
loff_t len);
void (*show_fdinfo)(struct seq_file *m, struct file *f);
#ifndef CONFIG_MMU
unsigned (*mmap_capabilities)(struct file *);
#endif
};
=================================
4.采用更方便方法注册字符设备
静态分配设备号需要我们检查当前系统中所有被使用了的设备号,然后挑选一个没有使用
的。而且静态分配设备号很容易带来冲突问题, Linux 社区推荐使用动态分配设备号,在注册字
符设备之前先申请一个设备号,系统会自动给你一个没有被使用的设备号,这样就避免了冲突。
卸载驱动的时候释放掉这个设备号。
int register_chrdev_region(dev_t from,
unsigned count,
const char *name)
//@from :要分配的设备编号范围的初始值,明确了主设备号和起始次设备号
//@count:次设备号个数
//@name:相关联的设备名称. (可在/proc/devices目录下查看到), 也即本组设备的驱动名称
//@return: 成功返回0,失败返回负数
int alloc_chrdev_region(dev_t *dev,
unsigned baseminor,
unsigned count,
const char *name)
/*******************************************
dev :保存申请到的设备号。
baseminor :次设备号起始地址,alloc_chrdev_region 可以申请一段连续的多个设备号,这
些设备号的主设备号一样,但是次设备号不同,次设备号以 baseminor 为起始地址地址开始递
增。一般 baseminor 为 0,也就是说次设备号从 0 开始。
count :要申请的设备号数量。
name :设备名字。
******************************************/
========================================
5.用户空间和内核空间的数据传递
copy_to_user()函数和copy_from_user()函数
unsigned long copy_to_user(void *to, const void *from, unsigned long n);
这个函数的作用是将内核空间的数据复制到用户空间。其中
to:目标地址(用户空间)
from:源地址(内核空间)
n:将要拷贝数据的字节数
返回:成功返回0,失败返回没有拷贝成功的数据字节数
unsigned long copy_from_user(void *to, const void *from, unsigned long n);
这个函数的作用是将用户空间的数据复制到内核空间。其中
to:目标地址(内核空间)
from:源地址(用户空间)
n:将要拷贝数据的字节数
返回:成功返回0,失败返回没有拷贝成功的数据字节数
==================================
6.代码示例
驱动代码示例:
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/fs.h>
#include <linux/slab.h>
#include <linux/uaccess.h>
#include <linux/io.h>
#define CHRDEVBASE_MAJOR 200 //主设备号
#define CHRDEVBASE_NAME "chrdevbase" //名字
static char readbuf[100]; /*读缓冲 */
static char writebuf[100]; /* 写缓冲 */
static char kerneldata[] = {"kernel data!"};
static int chrdevbase_open(struct inode *inode, struct file *filp)
{
// printk("chrdevbase_open\r\n");
return 0;
}
static int chrdevbase_release(struct inode *inode, struct file *filp)
{
// printk("chrdevbase_release\r\n");
return 0;
}
static ssize_t chrdevbase_read(struct file *filp, __user char *buf, size_t count,
loff_t *ppos)
{
int ret = 0;
//printk("chrdevbase_read\r\n");
memcpy(readbuf, kerneldata, sizeof(kerneldata));
ret = copy_to_user(buf, readbuf, count);
if(ret == 0) {
} else {
}
return 0;
}
static ssize_t chrdevbase_write(struct file *filp, const char __user *buf,
size_t count, loff_t *ppos)
{
int ret = 0;
//printk("chrdevbase_write\r\n");
ret = copy_from_user(writebuf, buf, count);
if(ret == 0) {
printk("kernel recevdata:%s\r\n", writebuf);
} else {
}
return 0;
}
/*
* 字符设备 操作集合
*/
static struct file_operations chrdevbase_fops={
.owner = THIS_MODULE,
.open = chrdevbase_open,
.release = chrdevbase_release,
.read = chrdevbase_read,
.write = chrdevbase_write,
};
static int __init chrdevbase_init(void)
{
int ret = 0;
printk("chrdevbase_init\r\n");
/* 注册字符设备 */
ret = register_chrdev(CHRDEVBASE_MAJOR, CHRDEVBASE_NAME, &chrdevbase_fops);
if(ret < 0) {
printk("chrdevbase init failed!\r\n");
}
return 0;
}
static void __exit chrdevbase_exit(void)
{
printk("chrdevbase_exit\r\n");
/* 注销字符设备 */
unregister_chrdev(CHRDEVBASE_MAJOR, CHRDEVBASE_NAME);
}
/*
模块入口与出口
*/
module_init(chrdevbase_init); /* 入口 */
module_exit(chrdevbase_exit); /* 出口 */
MODULE_LICENSE("GPL");
MODULE_AUTHOR("zuozhongkai");
================================
应用层代码示例:
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
/*
*argc:应用程序参数个数
*argv[]:具体的参数内容,字符串形式
*./chrdevbaseAPP <filename> <1:2> 1表示读,2表示写
* ./chrdevbaseAPP /dev/chrdevbase 1 表示从驱动里面读数据
* ./chrdevbaseAPP /dev/chrdevbase 2 表示向驱动里面写数据
* */
int main(int argc, char *argv[])
{
int ret = 0;
int fd = 0;
char *filename;
char readbuf[100], writebuf[100];
static char usrdata[] = {"usr data!"};
if(argc != 3) {
printf("Error usage!\r\n");
return -1;
}
filename = argv[1];
fd = open(filename, O_RDWR);
if(fd < 0 ) {
printf("Can't open file %s\r\n", filename);
return -1;
}
if(atoi(argv[2]) == 1){ /* 读 */
/* read */
ret = read(fd, readbuf, 50);
if (ret < 0) {
printf("read file %s failed!\r\n", filename);
}
else {
printf("APP read data:%s\r\n", readbuf);
}
}
/* write */
if(atoi(argv[2]) == 2) { /* 写 */
memcpy(writebuf, usrdata, sizeof(usrdata));
ret = write(fd, writebuf, 50);
if (ret < 0) {
printf("write file %s failed!\r\n", filename);
}
else {
}
}
/* close */
ret = close(fd);
if(ret < 0) {
printf("close file %s falied!\r\n", filename);
}
return 0 ;
}