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

    

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章