目錄:
先上代碼,後面一個一個解釋
#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