目录:
先上代码,后面一个一个解释
#include <linux/init.h>
#include <linux/module.h>
#include <linux/cdev.h>
#include <linux/fs.h>
#define HELLO_CDEV_NUM 1
static int hello_major = 0;
static const char devname[]="hellocdev";
static struct cdev hello_cdev;
dev_t dev;
int hello_open(struct inode *inode, struct file *filep)
{
printk(KERN_INFO "hello_open...\n");
return 0;
}
static struct file_operations hello_ops={
.open=hello_open,
};
static int __init hello_init(void)
{
int ret=0;
printk(KERN_INFO "hello_init...\n");
cdev_init(&hello_cdev, &hello_ops);
if(hello_major)
{
register_chrdev_region(MKDEV(hello_major,0), HELLO_CDEV_NUM, devname);
}else
{
ret=alloc_chrdev_region(&dev, 0, HELLO_CDEV_NUM, devname);
}
if(ret<0)
{
printk(KERN_INFO "alloc_chrdev_region failure: error code %d...\n",ret);
return ret;
}
printk(KERN_INFO "Major is:%u\nMinor is %u\n",MAJOR(dev),MINOR(dev));
ret=cdev_add(&hello_cdev, dev, HELLO_CDEV_NUM);
if(ret<0)
{
printk(KERN_INFO "cdev_add failure: error code %d...\n",ret);
return ret;
}
return 0;
}
static void __exit hello_exit(void)
{
cdev_del(&hello_cdev);
unregister_chrdev_region(dev,HELLO_CDEV_NUM);
printk(KERN_INFO "hello_exit...\n");
}
module_init(hello_init); //模块加载
module_exit(hello_exit); //模块卸载
MODULE_AUTHOR("Jump"); //作者名
MODULE_DESCRIPTION("this is hello cdev"); //模块描述
MODULE_LICENSE("Dual BSD/GPL"); //遵循协议
MODULE_ALIAS("hello cdev"); //模块别名
MODULE_VERSION("V1.0"); //模块版本号
基础知识:
1.模块初始化宏
module_init模块加载函数,也就是说是模块的入口,相当于main函数入口;与之相反的module_exit模块卸载函数;
__init 和 __exit 修饰用来告诉内核这两个函数只在内核加载和卸载的时候使用,而不能被调用,因为一旦加载和卸载完,
其修饰的函数所在存储空间将回收。
module_init()--- 模块加载函数(必须)
通过insmod或modprobe命令加载内核模块时,模块的加载函数会自动被内核执行,完成模块的相关初始化工作
module_exit()--- 模块卸载函数(必须)
当通过rmmod命令卸载某模块时,模块的卸载函数会自动被内核执行,完成与模块装载函数相反的功能2.上面有注释
MODULE_AUTHOR("Jump");
MODULE_DESCRIPTION("this is hello cdev");
MODULE_LICENSE("Dual BSD/GPL");
MODULE_ALIAS("hello cdev");
MODULE_VERSION("V1.0");
3.printk函数,用来调试打印用
Z:\kernel\linux-3.0.8\include\linux\printk.h
#define KERN_EMERG "<0>" /* system is unusable */#define KERN_ALERT "<1>" /* action must be taken immediately */
#define KERN_CRIT "<2>" /* critical conditions */
#define KERN_ERR "<3>" /* error conditions */
#define KERN_WARNING "<4>" /* warning conditions */
#define KERN_NOTICE "<5>" /* normal but significant condition */
#define KERN_INFO "<6>" /* informational */
#define KERN_DEBUG "<7>" /* debug-level messages */
int printk(const char *s, ...)
、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、
1.字符设备结构体:
所在路径://Z:\kernel\linux-3.0.8\include\linux\cdev.hstruct cdev {
struct kobject kobj; ////内嵌的内核对象. 不关心的,是由内核管理设备使用
struct module *owner; //cdev属于那个体module,一般写THIS_MODULE; 该字符设备所在的内核模块的对象指针.
const struct file_operations *ops; //操作集(设备驱动),是应用程序访问设备的驱动接口
struct list_head list; //内核链表:将当前cdev放到一链表,方便内核管理cdev
dev_t dev; //设备号,由主设备号和次设备号构成
unsigned int count; //在当设备下有多少个次设备...
};
1.设备号 dev_t dev: 设备号.每个字符设备都有自已的设备号,设备号在当内核中是唯一
所在路径:Z:\kernel\linux-3.0.8\include\linux\cdev.h
typedef __kernel_dev_t dev_t;
typedef __u32 __kernel_dev_t;
设备号dev由主设备号和次设备号组成:
主设备号:bit[31:20] 占用12bit
次设备号:bit[19:0] 占用20bit
所在路径:Z:\kernel\linux-3.0.8\include\linux\kdev_t.h
#define MINORBITS 20
#define MINORMASK ((1U << MINORBITS) - 1) //0x100000-1=0xfffff
#define MAJOR(dev) ((unsigned int) ((dev) >> MINORBITS)) //已知设备号得到主设备号
#define MINOR(dev) ((unsigned int) ((dev) & MINORMASK)) //已知设备号得到次设备号
#define MKDEV(ma,mi) (((ma) << MINORBITS) | (mi)) //已知主次设备号得到设备号
注册设备有两种方法:
方法1:指定设备号,固定写死,注意不能与现有的设备号相同
方法2:动态申请设备号,让内核为我们自动分配未使用的设备号
方法1:使用register_chrdev_region来注册设备号
/**
* register_chrdev_region() - register a range of device numbers
* @from: the first in the desired range of device numbers; must include
* the major number.
* @count: the number of consecutive device numbers required
* @name: the name of the device or driver.
*
* Return value is zero on success, a negative error code on failure.
*/
int register_chrdev_region(dev_t from, unsigned count, const char *name)
from:设备号
count:所需数量连续设备的数量,即次设备的个数
name:设备名
返回0表成功 负数表失败
方法2:使用alloc_chrdev_region来动态设备号
/**
* alloc_chrdev_region() - register a range of char device numbers
* @dev: output parameter for first assigned number
* @baseminor: first of the requested range of minor numbers
* @count: the number of minor numbers required
* @name: the name of the associated device or driver
*
* Allocates a range of char device numbers. The major number will be
* chosen dynamically, and returned (along with the first minor number)
* in @dev. Returns zero or a negative error code.
*/
int alloc_chrdev_region(dev_t *dev, unsigned baseminor, unsigned count,
const char *name)
dev:保存了内核为我们分配出来的设备号,当返回0时才有效
baseminor:第一个次设备号
count: 次设备号个数
name:设备名
返回0表成功 负数表失败
注销设备号
/**
* unregister_chrdev_region() - return a range of device numbers
* @from: the first in the range of numbers to unregister
* @count: the number of device numbers to unregister
*
* This function will unregister a range of @count device numbers,
* starting with @from. The caller should normally be the one who
* allocated those numbers in the first place...
*/
void unregister_chrdev_region(dev_t from, unsigned count)
from:要注销的设备号
count:注销的次设备号的个数
、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、
字符设备初始化:
Z:\kernel\linux-3.0.8\fs\char_dev.c
/**
* cdev_init() - initialize a cdev structure
* @cdev: the structure to initialize
* @fops: the file_operations for this device
*
* Initializes @cdev, remembering @fops, making it ready to add to the
* system with cdev_add().
*/
void cdev_init(struct cdev *cdev, const struct file_operations *fops)
cdev:传入字符设备结构体地址
fops:文件操作集
字符设备添加到内核:
Z:\kernel\linux-3.0.8\fs\char_dev.c
/**
* cdev_add() - add a char device to the system
* @p: the cdev structure for the device
* @dev: the first device number for which this device is responsible
* @count: the number of consecutive minor numbers corresponding to this
* device
*
* cdev_add() adds the device represented by @p to the system, making it
* live immediately. A negative error code is returned on failure.
*/
int cdev_add(struct cdev *p, dev_t dev, unsigned count)
p:传入字符设备结构体地址
dev:设备号
count:次设备个数
2.文件操作集:驱动程序为应用程序提供的一个操作接口
Z:\kernel\linux-3.0.8\include\linux\fs.h
struct file_operations {
struct module *owner;//拥有该结构的模块的指针,一般为THIS_MODULES
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 (*aio_read) (struct kiocb *, const struct iovec *, unsigned long, loff_t);//初始化一个异步的读取操作
ssize_t (*aio_write) (struct kiocb *, const struct iovec *, unsigned long, loff_t);//初始化一个异步的写入操作
int (*readdir) (struct file *, void *, filldir_t);//仅用于读取目录,对于设备文件,该字段为NULL
unsigned int (*poll) (struct file *, struct poll_table_struct *); //轮询函数,判断目前是否可以进行非阻塞的读写或写入
int (*ioctl) (struct inode *, struct file *, unsigned int, unsigned long); //执行设备I/O控制命令
long (*unlocked_ioctl) (struct file *, unsigned int, unsigned long); //不使用BLK文件系统,将使用此种函数指针代替ioctl
long (*compat_ioctl) (struct file *, unsigned int, unsigned long); //在64位系统上,32位的ioctl调用将使用此函数指针代替
int (*mmap) (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 *, struct dentry *, int datasync); //刷新待处理的数据
int (*aio_fsync) (struct kiocb *, int datasync); //异步刷新待处理的数据
int (*fasync) (int, struct file *, int); //通知设备FASYNC标志发生变化
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 **);
};
在来看下Makefile:
网上很多,看下这个点击打开链接 https://blog.csdn.net/zqixiao_09/article/details/50838043
ifeq ($(KERNELRELEASE),)
KERNELPATH:=/usr/src/linux-3.0.8/
PWD:=$(shell pwd)
all:
$(MAKE) -C $(KERNELPATH) M=$(PWD) modules
clean:
rm -rf *.obj *.o *.bak *.ko *.order *.symvers
else
obj-m:=hello.o
endif
在虚拟机make
jump@mylubuntu:/home/mysmbshare/kernel/cdevdriver/hello$ make
make -C /usr/src/linux-3.0.8/ M=/home/mysmbshare/kernel/cdevdriver/hello modules
make[1]: Entering directory '/usr/src/linux-3.0.8'
CC [M] /home/mysmbshare/kernel/cdevdriver/hello/hello.o
Building modules, stage 2.
MODPOST 1 modules
CC /home/mysmbshare/kernel/cdevdriver/hello/hello.mod.o
LD [M] /home/mysmbshare/kernel/cdevdriver/hello/hello.ko
make[1]: Leaving directory '/usr/src/linux-3.0.8'
测试模块:
hello_user.c
#include <unistd.h>
#include <stdio.h>
#include <stdint.h>
#include <stdlib.h>
int main(void)
{
int fd;
fd = open("/dev/hellocdev",O_RDWR | O_NONBLOCK);
if(fd<0)
{
printf("open fail...%d",fd);
exit(1);
}
printf("open true...%d\n",fd);
close(fd);
return 0;
Makefile:
KERNELDIR?=/user/src/linux-3.0.8/
all: hello_user
hello_user : hello_user.c
arm-linux-gcc -I$(KERNELDIR) -o $@ $^
clean :
rm -f hello_user
在开发板上安装模块:
[root@FriendlyARM hello_user]# insmod ../hello/hello.ko
[ 386.973527] hello_init...
[ 386.973566] Major is:250
[ 386.973569] Minor is 0
[root@FriendlyARM hello_user]# lsmod | grep "hello"
hello 1264 0 - Live 0xbf1e5000
cat /proc/devices 查看是否安装成功,以及主从设备号
创建字符设备驱动文件:
[root@FriendlyARM hello_user]# mknod /dev/hellocdev c 250 0
运行测试程序:
[root@FriendlyARM hello_user]# ./hello_user
[ 1773.411860] hello_open...
open true...3