linux 字符设备驱动的编写基础

目录:


先上代码,后面一个一个解释

#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.h
struct 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

    

猜你喜欢

转载自blog.csdn.net/u011171361/article/details/79688474
今日推荐