字符設備驅動(一)框架



title: 字符設備驅動(一)框架
tags: linux
date: 2018-11-19 22:40:11
toc: true

字符設備驅動(一)框架

命令速記

mknod  file c major cnt #創建字符設備
rmmod  file				#卸載驅動
insmod file				#安裝驅動
lsmod					#查看安裝的驅動
mount -t nfs -o nolock 192.168.5.222:/home/book/stu /mnt #掛載nfs
mount -o nolock,rsize=1024,wsize=1024  192.168.137.222:/home/book/stu  /mnt


ifconfig eth0 192.168.5.200
showmount -e #查看主機允許的掛載

框架結構

LinuxApp通過調用open/close等庫函數去控制硬件設備.

  1. open,write,read這些是系統的接口函數,由C庫實現
  2. 調用C庫的這些函數時,會觸發 swi x指令,引發異常,進入異常處理函數稱為 system call interface
  3. system call interface會去調用 system open/write/read----稱為virtual Filesystem 虛擬文件系統
  4. system open/write/read根據不同的文件調用不同的驅動程序

mark

數據結構

  1. 驅動程序創建了按照 字符設備屬性+主設備號+次設備號操作的接口
  2. 需要有一個設備文件,他的屬性是字符設備+驅動對應的主設備號
  3. app操作這個設備文件,獲取其屬性,操作設備文件時,內核調用相應的驅動程序接口
  4. 注意app操作的設備的屬性一定是要等同於驅動要求的設備屬性,否則依然是無法找到設備的

接口實現

App使用open等函數調用驅動,open這一類函數是定義在fs.h中的struct file_operations ,這是一個統一的接口.所以一個驅動程序需要按照這個格式提供相應的接口即可

*
 * NOTE:
 * read, write, poll, fsync, readv, writev, unlocked_ioctl and compat_ioctl
 * can be called without the big kernel lock held in all filesystems.
 */
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 (*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);
	unsigned int (*poll) (struct file *, struct poll_table_struct *);
	int (*ioctl) (struct inode *, struct file *, unsigned int, unsigned long);
	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 (*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);
	int (*lock) (struct file *, int, struct file_lock *);
	ssize_t (*sendfile) (struct file *, loff_t *, size_t, read_actor_t, void *);
	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 (*dir_notify)(struct file *filp, unsigned long arg);
	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);
};
struct module *owner
第一個 file_operations 成員根本不是一個操作; 它是一個指向擁有這個結構的模塊的指針. 這個成員用來在它的操作還在被使用時阻止模塊被卸載. 幾乎所有時間中, 它被簡單初始化為 THIS_MODULE, 一個在 <linux/module.h> 中定義的宏.

loff_t (*llseek) (struct file *, loff_t, int);
llseek 方法用作改變文件中的當前讀/寫位置, 並且新位置作為(正的)返回值. loff_t 參數是一個"long offset", 並且就算在 32位平台上也至少 64 位寬. 錯誤由一個負返回值指示. 如果這個函數指針是 NULL, seek 調用會以潛在地無法預知的方式修改 file 結構中的位置計數器( 在"file 結構" 一節中描述).

ssize_t (*read) (struct file *, char __user *, size_t, loff_t *);
用來從設備中獲取數據. 在這個位置的一個空指針導致 read 系統調用以 -EINVAL("Invalid argument") 失敗. 一個非負返回值代表了成功讀取的字節數( 返回值是一個 "signed size" 類型, 常常是目標平台本地的整數類型).

ssize_t (*aio_read)(struct kiocb *, char __user *, size_t, loff_t);
初始化一個異步讀 -- 可能在函數返回前不結束的讀操作. 如果這個方法是 NULL, 所有的操作會由 read 代替進行(同步地).

ssize_t (*write) (struct file *, const char __user *, size_t, loff_t *);
發送數據給設備. 如果 NULL, -EINVAL 返回給調用 write 系統調用的程序. 如果非負, 返回值代表成功寫的字節數.

ssize_t (*aio_write)(struct kiocb *, const char __user *, size_t, loff_t *);
初始化設備上的一個異步寫.

int (*readdir) (struct file *, void *, filldir_t);
對於設備文件這個成員應當為 NULL; 它用來讀取目錄, 並且僅對文件系統有用.

unsigned int (*poll) (struct file *, struct poll_table_struct *);
poll 方法是 3 個系統調用的后端: poll, epoll, 和 select, 都用作查詢對一個或多個文件描述符的讀或寫是否會阻塞. poll 方法應當返回一個位掩碼指示是否非阻塞的讀或寫是可能的, 並且, 可能地, 提供給內核信息用來使調用進程睡眠直到 I/O 變為可能. 如果一個驅動的 poll 方法為 NULL, 設備假定為不阻塞地可讀可寫.

int (*ioctl) (struct inode *, struct file *, unsigned int, unsigned long);
ioctl 系統調用提供了發出設備特定命令的方法(例如格式化軟盤的一個磁道, 這不是讀也不是寫). 另外, 幾個 ioctl 命令被內核識別而不必引用 fops 表. 如果設備不提供 ioctl 方法, 對於任何未事先定義的請求(-ENOTTY, "設備無這樣的 ioctl"), 系統調用返回一個錯誤.

int (*mmap) (struct file *, struct vm_area_struct *);
mmap 用來請求將設備內存映射到進程的地址空間. 如果這個方法是 NULL, mmap 系統調用返回 -ENODEV.

int (*open) (struct inode *, struct file *);
盡管這常常是對設備文件進行的第一個操作, 不要求驅動聲明一個對應的方法. 如果這個項是 NULL, 設備打開一直成功, 但是你的驅動不會得到通知.

int (*flush) (struct file *);
flush 操作在進程關閉它的設備文件描述符的拷貝時調用; 它應當執行(並且等待)設備的任何未完成的操作. 這個必須不要和用戶查詢請求的 fsync 操作混淆了. 當前, flush 在很少驅動中使用; SCSI 磁帶驅動使用它, 例如, 為確保所有寫的數據在設備關閉前寫到磁帶上. 如果 flush 為 NULL, 內核簡單地忽略用戶應用程序的請求.

int (*release) (struct inode *, struct file *);
在文件結構被釋放時引用這個操作. 如同 open, release 可以為 NULL.

int (*fsync) (struct file *, struct dentry *, int);
這個方法是 fsync 系統調用的后端, 用戶調用來刷新任何掛着的數據. 如果這個指針是 NULL, 系統調用返回 -EINVAL.

int (*aio_fsync)(struct kiocb *, int);
這是 fsync 方法的異步版本.

int (*fasync) (int, struct file *, int);
這個操作用來通知設備它的 FASYNC 標志的改變. 異步通知是一個高級的主題, 在第 6 章中描述. 這個成員可以是NULL 如果驅動不支持異步通知.

int (*lock) (struct file *, int, struct file_lock *);
lock 方法用來實現文件加鎖; 加鎖對常規文件是必不可少的特性, 但是設備驅動幾乎從不實現它.

ssize_t (*readv) (struct file *, const struct iovec *, unsigned long, loff_t *);
ssize_t (*writev) (struct file *, const struct iovec *, unsigned long, loff_t *);
這些方法實現發散/匯聚讀和寫操作. 應用程序偶爾需要做一個包含多個內存區的單個讀或寫操作; 這些系統調用允許它們這樣做而不必對數據進行額外拷貝. 如果這些函數指針為 NULL, read 和 write 方法被調用( 可能多於一次 ).

ssize_t (*sendfile)(struct file *, loff_t *, size_t, read_actor_t, void *);
這個方法實現 sendfile 系統調用的讀, 使用最少的拷貝從一個文件描述符搬移數據到另一個. 例如, 它被一個需要發送文件內容到一個網絡連接的 web 服務器使用. 設備驅動常常使 sendfile 為 NULL.

ssize_t (*sendpage) (struct file *, struct page *, int, size_t, loff_t *, int);
sendpage 是 sendfile 的另一半; 它由內核調用來發送數據, 一次一頁, 到對應的文件. 設備驅動實際上不實現 sendpage.

unsigned long (*get_unmapped_area)(struct file *, unsigned long, unsigned long, unsigned long, unsigned long);
這個方法的目的是在進程的地址空間找一個合適的位置來映射在底層設備上的內存段中. 這個任務通常由內存管理代碼進行; 這個方法存在為了使驅動能強制特殊設備可能有的任何的對齊請求. 大部分驅動可以置這個方法為 NULL.[10]

int (*check_flags)(int)
這個方法允許模塊檢查傳遞給 fnctl(F_SETFL...) 調用的標志.

int (*dir_notify)(struct file *, unsigned long);
這個方法在應用程序使用 fcntl 來請求目錄改變通知時調用. 只對文件系統有用; 驅動不需要實現 dir_notify.

其中用到了 struct file表示打開的文件,具體的點這里,Struct inode表示一個磁盤上的具體文件.

struct file {
	/*
	 * fu_list becomes invalid after file_free is called and queued via
	 * fu_rcuhead for RCU freeing
	 */
	union {
		struct list_head	fu_list;
		struct rcu_head 	fu_rcuhead;
	} f_u;
	struct path		f_path;
#define f_dentry	f_path.dentry
#define f_vfsmnt	f_path.mnt
	const struct file_operations	*f_op;
	atomic_t		f_count;
	unsigned int 		f_flags;
	mode_t			f_mode;
	loff_t			f_pos;
	struct fown_struct	f_owner;
	unsigned int		f_uid, f_gid;
	struct file_ra_state	f_ra;

	unsigned long		f_version;
#ifdef CONFIG_SECURITY
	void			*f_security;
#endif
	/* needed for tty driver, and maybe others */
	void			*private_data;

#ifdef CONFIG_EPOLL
	/* Used by fs/eventpoll.c to link all the hooks to this file */
	struct list_head	f_ep_links;
	spinlock_t		f_ep_lock;
#endif /* #ifdef CONFIG_EPOLL */
	struct address_space	*f_mapping;
};

驅動注冊

實現接口后需要告知內核,也就是注冊接口,注冊到內核中的函數原型如下:

/**
 * register_chrdev() - Register a major number for character devices.
 * @major: major device number or 0 for dynamic allocation
 * @name: name of this range of devices
 * @fops: file operations associated with this devices
 *
 * If @major == 0 this functions will dynamically allocate a major and return
 * its number.
 *
 * If @major > 0 this function will attempt to reserve a device with the given
 * major number and will return zero on success.
 *
 * Returns a -ve errno on failure.
 *
 * The name of this device has nothing to do with the name of the device in
 * /dev. It only helps to keep track of the different owners of devices. If
 * your module name has only one type of devices it's ok to use e.g. the name
 * of the module here.
 *
 * This function registers a range of 256 minor numbers. The first minor number
 * is 0.
 */
int register_chrdev(unsigned int major, const char *name,
		    const struct file_operations *fops)

major 為主設備號,name為字符串標識,fops就是包含所有接口函數的結構體file_operations 這里補充下設備號的知識.在之前的busybox 之 最小根文件系統中我們使用mknod來創建字符設備或者塊設備(/dev/console).

# ls -l /dev/console
crw-rw----    1 0        0          5,   1 Jan  1 01:05 /dev/console

mark

如何使系統去調用這個注冊函數?

這里使用宏 module_init來實現,module_init 就是定義一個結構體,這個結構體中有一個函數指針,指向“入口函數”。 當安裝一個驅動程序時,內核會自動找到這樣一個結構體,調用里面的函數指針,入口函數.也就是當我們執行命令去加載驅動的時候,會去遍歷這些指針應該.

先了解自上而下的調用過程

  1. app自主去open("dev/xxx"),這里的xxx設備文件為字符設備,它有標識為字符設備,以及主設備號和次設備號
  2. VFS 系統根據xxx的屬性,即字符設備類型+主設備號找到注冊到的file_operations結構,調用函數

函數原理

綜上, register_chrdev函數最簡單的實現方式也就是存在一個chardev的數組,按照主設備號為索引,內容為file_operations指針即可.所謂注冊也就是填充這個數組即可

小結

register_chrdev是實現注冊的方式, module_init是系統在裝載驅動的時候去尋找到這個注冊的方式.為什么不直接在 register_chrdev中直接實現 module_init的通知功能.可以這么理解,裝載驅動的時候可能還需要其他一些動作,系統提供的 register_chrdev只是在這個chardev數組增加,並沒有辦法做其他事情,所以這里一般是以下這種形式

void my_regist_fun()
{
	dosomething();
    register_chrdev(major,name,&my_file_operations);
}
module_init(my_regist_fun);

驅動卸載

同驅動加載注冊一樣,使用unregister_chrdev實現,使用module_exit來使得內核能夠主動調用

int unregister_chrdev(unsigned int major, const char *name)

同樣的一般實現形式如下

void my_unregist_fun()
{
	dosomething();
    unregister_chrdev(major,name);
}
module_init(my_regist_fun);

程序設計

(一)手動創建主設備號

  1. 頭文件包含

    #include <linux/module.h>
    #include <linux/kernel.h>		//內核相關
    #include <linux/fs.h>			//文件操作相關結構提
    #include <linux/init.h>
    #include <linux/delay.h>
    #include <asm/uaccess.h>		//內核與用戶數據交互 
    #include <asm/irq.h>
    #include <asm/io.h>				//ioremap等 io訪問
    #include <asm/arch/regs-gpio.h>
    #include <asm/hardware.h>
    
  2. Makefile 中需要提前編譯好內核,並且加入其中.-C是指使用KERN_DIR中的Makefile來編譯,M表示當前目錄,moudle也就是目標了

    KERN_DIR = /work/system/linux-2.6.22.6
    all:
    	make -C $(KERN_DIR) M=`pwd` modules 
    clean:
    	make -C $(KERN_DIR) M=`pwd` modules clean
    	rm -rf modules.order
    obj-m	+= first_drv.o
    
  3. 添加 GPL 的licence,否則在安裝的時候會有警告

    MODULE_LICENSE("GPL");
    
    # insmod ./dri.ko
    dri: module license 'unspecified' taints kernel.
    
  4. 使用cat /proc/devices查看掛載的驅動設備

    # cat /proc/devices
    Character devices:
      1 mem
      ...
    

代碼一覽

// 驅動程序 dri.c
#include <linux/module.h>
#include <linux/kernel.h>		//內核相關
#include <linux/fs.h>			//文件操作相關結構提
#include <linux/init.h>
#include <linux/delay.h>
#include <asm/uaccess.h>		//內核與用戶數據交互 
#include <asm/irq.h>
#include <asm/io.h>				//ioremap等 io訪問
#include <asm/arch/regs-gpio.h>
#include <asm/hardware.h>

int first_drv_open(struct inode *inode, struct file *file)
{
	printk("first_drv_open\n");
	return 0;
}
ssize_t first_drv_write(struct file *file, const char __user *buf, size_t count, loff_t * ppos)
{
	printk("first_drv_write\n");
	return 0;
}
struct file_operations first_drv_fops = {
    .open   =   first_drv_open,     
	.write	=	first_drv_write,	   
};
int first_drv_init(void)
{
	register_chrdev(111, "first_drv", &first_drv_fops); 
	return 0;
}
void first_drv_exit(void)
{
	unregister_chrdev(111, "first_drv");  
}

module_init(first_drv_init);
module_exit(first_drv_exit);
MODULE_LICENSE("GPL");

測試程序test.c,編譯命令是arm-linux-gcc -o test test.c

#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>
int main(int argc, char **argv)
{
	int fd;
	int val = 1;
	fd = open("xxx", O_RDWR);
	if (fd < 0)
	{
		printf("can't open!\n");
	}
	write(fd, &val, 4);
	return 0;
}

測試

  1. 安裝驅動insmod dri.ko,如果之前有安裝過,使用rmmod dri.ko卸載驅動

  2. 查看驅動是否安裝 cat /proc/devices ,這里能夠看到主設備號是111,設備名是first_drv

    Character devices:
    ...
    111 first_drv
    ...
    
  3. 創建測試所需的設備文件xxx,這里的xxx的主設備號必須與驅動的一致,否則打開錯誤,內核是根據主設備號查找的,而非名字

    mknod xyz c 111 5 #111 是主設備號,5是次設備號
    
  4. 執行測試程序./test,如果沒有步驟3創建設備文件,會提示沒有設備文件的

    # ./test
    first_drv_open
    first_drv_write
    

(二)自動分配主設備號

注冊函數中使用major=0參數傳遞給注冊函數 register_chrdev時,系統就能自動尋找空閑的一個主設備號返回.

int major;
int first_drv_init(void)
{
	major=register_chrdev(0, "first_drv", &first_drv_fops); 
	return 0;
}
void first_drv_exit(void)
{
	unregister_chrdev(major, "first_drv");  
}

這個時候可以手動cat /proc/devices去查看設備號然后去創建設備去控制,使用cat /proc/devices查看設備號然后去操作

(三)自動創建設備文件

busybox 完善(四)中提到了mdev機制,也就是系統自動掛載,mdev會根據sys下的信息自動創建設備的,自動在/proc/sys/kernel/hotplug完成加載與卸載

# cat init.d/rcS
mount -a
mkdir /dev/pts
mount -t devpts devpts /dev/pts
echo /sbin/mdev > /proc/sys/kernel/hotplug		#這里實現熱拔插自動加載信息
mdev -s

我們這里的是會在sys下創建一個class的文件夾的

代碼一覽

設計流程

static struct class *firstdrv_class;
static struct class_device	*firstdrv_class_dev;

static struct file_operations first_drv_fops = {
	.owner  =   THIS_MODULE,    /* 這是一個宏,推向編譯模塊時自動創建的__this_module變量 */
	.open=.....
	.write=...
}

//創建一個類
firstdrv_class = class_create(THIS_MODULE, "firstdrv");
//在類下面去創建一個設備 /dev/xyz 
firstdrv_class_dev = class_device_create(firstdrv_class, NULL, MKDEV(major, 0), NULL, "xyz"); /* 

//卸載設備需要刪除這個類
class_device_unregister(firstdrv_class_dev);
class_destroy(firstdrv_class);

注意 如果沒有加入MODULE_LICENSE("GPL");,會提示

# insmod dri.ko
dri: Unknown symbol class_device_create
dri: Unknown symbol class_device_unregister
dri: Unknown symbol class_create
dri: Unknown symbol class_destroy
insmod: cannot insert 'dri.ko': Unknown symbol in module (-1): No such file or directory

完整代碼

#include <linux/module.h>
#include <linux/kernel.h>		//內核相關
#include <linux/fs.h>			//文件操作相關結構提
#include <linux/init.h>
#include <linux/delay.h>
#include <asm/uaccess.h>		//內核與用戶數據交互 
#include <asm/irq.h>
#include <asm/io.h>				//ioremap等 io訪問
#include <asm/arch/regs-gpio.h>
#include <asm/hardware.h>

static struct class *firstdrv_class;
static struct class_device	*firstdrv_class_dev;

static int first_drv_open(struct inode *inode, struct file *file)
{
	printk("first_drv_open\n");
	return 0;
}
static ssize_t first_drv_write(struct file *file, const char __user *buf, size_t count, loff_t * ppos)
{
	printk("first_drv_write\n");
	return 0;
}
static struct file_operations first_drv_fops = {
	.owner  =   THIS_MODULE,    /* 這是一個宏,推向編譯模塊時自動創建的__this_module變量 */
    .open   =   first_drv_open,     
	.write	=	first_drv_write,	   
};
static int major;
static int first_drv_init(void)
{
	major=register_chrdev(0, "first_drv", &first_drv_fops); // 注冊, 告訴內核

	firstdrv_class = class_create(THIS_MODULE, "first_drv");
	firstdrv_class_dev = class_device_create(firstdrv_class, NULL, MKDEV(major, 0), NULL, "xyz"); /* /dev/xyz */
	return 0;
}
static void first_drv_exit(void)
{
	unregister_chrdev(major, "first_drv"); // 卸載
	class_device_unregister(firstdrv_class_dev);
	class_destroy(firstdrv_class);
}

module_init(first_drv_init);
module_exit(first_drv_exit);
MODULE_LICENSE("GPL");

測試

  1. 安裝insmod dri.ko

  2. 查看下是否加載,這里被自動分配了252這個主設備號

    # cat /proc/devices
    ...
    252 first_drv
    ...
    # lsmod
    Module                  Size  Used by    Tainted: P
    dri                     2124  0
    
  3. 運行./test

    # ./test
    first_drv_open
    first_drv_write
    

分析

系統會自動在/sys/class下創建class下的文件信息first_drv,可以看到在/sys/class/first_drv/xyz/dev中的文件內容就是其主設備號+次設備號

# cd /sys/
# ls
block     class     firmware  kernel    power
bus       devices   fs        module

# ls class
first_drv     mem           ppdev         scsi_host     usb_hostt
# ls /sys/class/first_drv/
xyz
# ls /sys/class/first_drv/xyz
dev        subsystem  uevent
# cat  /sys/class/first_drv/xyz/dev
252:0

(四)使用次設備號

在(三)自動分配主設備號中,是在class結構下掛接具體的class_device,這里更改為在同一個class下掛接多個class_device.

//定義一個類下面有四個對象,也就是4個子設備了--------------------------------------
static struct class *firstdrv_class;
static struct class_device	*firstdrv_class_dev[4];
//注冊生成次設備號-------------------------------------------------------------
major=register_chrdev(0, "first_drv", &first_drv_fops); // 注冊, 告訴內核
firstdrv_class = class_create(THIS_MODULE, "first_drv");
for (minor = 0; minor < 4; minor++)
{
	firstdrv_class_dev[minor] = class_device_create(firstdrv_class, NULL, MKDEV(major, minor), NULL, "xyz%d", minor);
}
//卸載設備----------------------------------------------------------------------
unregister_chrdev(major, "first_drv"); // 卸載
for ( minor = 0; minor < 4; minor++)
{
    class_device_unregister(firstdrv_class_dev[minor]);
}
class_destroy(firstdrv_class);

那么如何區分出次設備號呢?

//在讀寫函數中,使用file結構指針中的參數
int minor =  MINOR(file->f_dentry->d_inode->i_rdev);
//在open函數中,使用node節點
int minor = MINOR(inode->i_rdev);

代碼一覽

完整的驅動函數代碼

#include <linux/module.h>
#include <linux/kernel.h>		//內核相關
#include <linux/fs.h>			//文件操作相關結構提
#include <linux/init.h>
#include <linux/delay.h>
#include <asm/uaccess.h>		//內核與用戶數據交互 
#include <asm/irq.h>
#include <asm/io.h>				//ioremap等 io訪問
#include <asm/arch/regs-gpio.h>
#include <asm/hardware.h>

static struct class *firstdrv_class;
static struct class_device	*firstdrv_class_dev[4];

static int first_drv_open(struct inode *inode, struct file *file)
{
	int minor = MINOR(inode->i_rdev);
	printk("first_drv_open=%d\n",minor);

	return 0;
}
static ssize_t first_drv_write(struct file *file, const char __user *buf, size_t count, loff_t * ppos)
{
	int minor =  MINOR(file->f_dentry->d_inode->i_rdev);
	printk("first_drv_write=%d\n",minor);
	return 0;
}
static struct file_operations first_drv_fops = {
	.owner  =   THIS_MODULE,    /* 這是一個宏,推向編譯模塊時自動創建的__this_module變量 */
    .open   =   first_drv_open,     
	.write	=	first_drv_write,	   
};
static int major;
static int first_drv_init(void)
{
	int minor=0;
	major=register_chrdev(0, "first_drv", &first_drv_fops); // 注冊, 告訴內核

	firstdrv_class = class_create(THIS_MODULE, "first_drv");

	for (minor = 0; minor < 4; minor++)
	{
		firstdrv_class_dev[minor] = class_device_create(firstdrv_class, NULL, MKDEV(major, minor), NULL, "xyz%d", minor);
	}


	return 0;
}
static void first_drv_exit(void)
{
	int minor = 0;
	unregister_chrdev(major, "first_drv"); // 卸載
	for ( minor = 0; minor < 4; minor++)
	{
		class_device_unregister(firstdrv_class_dev[minor]);
	}
	class_destroy(firstdrv_class);
}
module_init(first_drv_init);
module_exit(first_drv_exit);
MODULE_LICENSE("GPL");

更改下測試代碼,使用參數傳遞所需打開的文件

#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>
int main(int argc, char **argv)
{
	int fd;
	int val = 1;
	if (argc <= 1)  {printf("nofile select !\n"); return ;}
	fd = open(argv[1], O_RDWR);
	if (fd < 0)
	{
		printf("can't open!\n");
	}
	write(fd, &val, 4);
	return 0;
}

測試

  1. 首先安裝驅動insmod dri.ko

  2. 查詢驅動

    # lsmod
    Module                  Size  Used by    Tainted: P
    dri                     2232  0
    # cat /proc/devices
    Character devices:
    252 first_drv
    
  3. 查詢掛載設備

    # ls /dev/xyz*
    /dev/xyz0  /dev/xyz1  /dev/xyz2  /dev/xyz3
    
  4. 測試

    # ./test /dev/xyz0
    first_drv_open=0
    first_drv_write=0
    # ./test /dev/xyz1
    first_drv_open=1
    first_drv_write=1
    # ./test /dev/xyz2
    first_drv_open=2
    first_drv_write=2
    

分析

查看下sys/class下是否自動創建了xyz的相關信息

# ls /sys/class/
first_drv 

# ls /sys/class/first_drv/xyz*
/sys/class/first_drv/xyz0:
dev        subsystem  uevent
/sys/class/first_drv/xyz1:
dev        subsystem  uevent
/sys/class/first_drv/xyz2:
dev        subsystem  uevent
/sys/class/first_drv/xyz3:
dev        subsystem  uevent

# cat  /sys/class/first_drv/xyz*/dev
252:0
252:1
252:2
252:3

(五)異常捕獲處理

注冊時的異常捕獲

ret = register_chrdev(LED_MAJOR, DEVICE_NAME, &s3c24xx_leds_fops);
if (ret < 0) {
    printk(DEVICE_NAME " can't register major number\n");
    return ret;
}

使用類來調用mdev機制創建類

leds_class = class_create(THIS_MODULE, "leds");
if (IS_ERR(leds_class))
    return PTR_ERR(leds_class);

類下掛載設備

leds_class_devs[minor] = class_device_create(leds_class, NULL, MKDEV(LED_MAJOR, minor), NULL, "led%d", minor);
if (unlikely(IS_ERR(leds_class_devs[minor])))
{
	return PTR_ERR(leds_class_devs[minor]);
}			

(六)驅動信息

/* 描述驅動程序的一些信息,不是必須的 */
MODULE_AUTHOR("http://www.100ask.net");
MODULE_VERSION("0.1.0");
MODULE_DESCRIPTION("S3C2410/S3C2440 LED Driver");
MODULE_LICENSE("GPL");

小結

  • 應用程序通過open/write/close調用,open等通過傳遞的文件的主設備號去尋找驅動,執行驅動函數.
  • 驅動程序中使用register_chrdev來注冊驅動,使用class_deviceclass相關函數調用mdev機制達到自動創建設備文件的目的
  • 主設備號是提供給系統用的,次設備號是提供給用戶用的,可以當作一個參數使用
  1. 定義file_operations結構體,填充打開,讀寫函數

  2. 定義注冊函數與卸載函數

  3. 修飾注冊函數與卸載函數module_init,module_exit

  4. 使用mdev需要創建class類,並在class類下掛載對應的class_device設備節點

  5. 創建驅動信息MODULE_LICENSE("GPL");

  6. 頭文件包含

    #include <linux/module.h>
    #include <linux/kernel.h>		//內核相關
    #include <linux/fs.h>			//文件操作相關結構提
    #include <linux/init.h>
    #include <linux/delay.h>
    #include <asm/uaccess.h>		//內核與用戶數據交互 
    #include <asm/irq.h>
    #include <asm/io.h>				//ioremap等 io訪問
    #include <asm/arch/regs-gpio.h>
    #include <asm/hardware.h>
    
  7. Makefile

    KERN_DIR = /work/system/linux-2.6.22.6
    all:
    	make -C $(KERN_DIR) M=`pwd` modules 
    clean:
    	make -C $(KERN_DIR) M=`pwd` modules clean
    	rm -rf modules.order
    obj-m	+= first_drv.o
    

注意:

視頻順序是LED驅動程序_操作LED 然后是LED驅動程序_測試改進


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM