国产 无码 综合区,色欲AV无码国产永久播放,无码天堂亚洲国产AV,国产日韩欧美女同一区二区

Linux下字符設備驅動開發(fā)以及流程介紹

這篇具有很好參考價值的文章主要介紹了Linux下字符設備驅動開發(fā)以及流程介紹。希望對大家有所幫助。如果存在錯誤或未考慮完全的地方,請大家不吝賜教,您也可以點擊"舉報違法"按鈕提交疑問。


首先我們介紹一下什么是字符設備,然后講解一下字符設備開發(fā)的具體的流程,分別詳細介紹每一個流程中涉及到的結構體以及知識點,最后我們編寫代碼實現字符設備的開發(fā)以及測試。


1 - 字符設備介紹

Linux內核設計哲學是把所有的東西都抽象成文件進行訪問,這樣對設備的訪問都是通過文件I/O來進行
操作。Linux內核將設備按照訪問特性分為三類:字符設備、塊設備、網絡設備。

linux字符設備驅動程序編寫流程,# IGKBoard(imx6ull)驅動開發(fā),驅動開發(fā),linux,嵌入式硬件,c語言,物聯(lián)網

字符設備對數據的處理按照字節(jié)流的形式進行的。典型的字符設備:串口、鍵盤、觸摸屏、攝像頭、I2C、SPI、聲卡等;應用程序能夠使用系統(tǒng)IO函數open、write、read、lseek、close…來就行訪問。

如下圖:應用程序運行在用戶空間,而Linux驅動屬于內核一部分,因此驅動運行于內核空間,當用戶想要實現對內核操作時,必須使用系統(tǒng)調用來實現從用戶空間到內核空間的操作。

linux字符設備驅動程序編寫流程,# IGKBoard(imx6ull)驅動開發(fā),驅動開發(fā),linux,嵌入式硬件,c語言,物聯(lián)網


2 - 字符設備開發(fā)流程圖

linux字符設備驅動程序編寫流程,# IGKBoard(imx6ull)驅動開發(fā),驅動開發(fā),linux,嵌入式硬件,c語言,物聯(lián)網
我們創(chuàng)建一個字符設備的時候,首先要的到一個設備號,分配設備號的途徑有靜態(tài)分配和動態(tài)分配;拿到設備的唯一 ID,我們需要實現 file_operation 并保存到 cdev 中,實現 cdev 的初始化;然后我們需要將我們所做的工作告訴內核,使用 cdev_add() 注冊 cdev;最后我們還需要創(chuàng)建設備節(jié)點,以便我們后面調用 file_operation 接口。

注銷設備時我們需釋放內核中的 cdev,歸還申請的設備號,刪除創(chuàng)建的設備節(jié)點。

linux字符設備驅動程序編寫流程,# IGKBoard(imx6ull)驅動開發(fā),驅動開發(fā),linux,嵌入式硬件,c語言,物聯(lián)網


3 - 字符設備開發(fā)流程具體講解

(1)設備編號的定義與申請

【1】Linux主次設備號介紹

字符設備通過文件系統(tǒng)中的設備名來存取,慣例上它們位于/dev目錄。

wangdengtao@wangdengtao-virtual-machine:~$ ls -l /dev/
總用量 0
crw-------  1 root        root     10, 124  317 18:48 cpu_dma_latency
crw-------  1 root        root     10, 203  317 18:48 cuse
...

我們可以看見上面的兩個設備,首先最前面的‘c’表示這是一個字符(character)設備;我們可以看見第二個root后面的數字,這些數字是給特殊設備的主次設備編號。10,就是主設備號,后面的124和203就是次設備號。如果我們想要對相關設備進行操作,只需要對設備文件進行讀或者寫操作就可以了。

傳統(tǒng)上,主編號標識設備相連的驅動;次設備號被內核來決定應用哪個設備。

dev_t 是一個32位的數據類型,其中高 12 位為主設備號,低 20 位為次設備號。在編碼時,我們不應該管哪些位是主設備號,哪些位是次設備號。而是應當利用在<linux/kdev_t.h>中的一套宏定義來獲取一個dev_t的主、次編號:

wangdengtao@wangdengtao-virtual-machine:~/imx6ull/imx6ull/bsp/kernel/linux-imx/include/linux$ cat kdev_t.h 
/* SPDX-License-Identifier: GPL-2.0 */
#ifndef _LINUX_KDEV_T_H
#define _LINUX_KDEV_T_H

#include <uapi/linux/kdev_t.h>

#define MINORBITS	20
#define MINORMASK	((1U << MINORBITS) - 1)

#define MAJOR(dev)	((unsigned int) ((dev) >> MINORBITS))
#define MINOR(dev)	((unsigned int) ((dev) & MINORMASK))
#define MKDEV(ma,mi)	(((ma) << MINORBITS) | (mi))
...

MINORBITS 表示次設備號位數,一共是 20 位。
MINORMASK 表示次設備號掩碼。
MAJOR 用于從 dev_t 中獲取主設備號,將 dev_t 右移 20 位即可。
MINOR 用于從 dev_t 中獲取次設備號,取 dev_t 的低 20 位的值即可。
MKDEV 用于將給定的主設備號和次設備號的值組合成 dev_t 類型的設備號。

【2】分配設備編號

靜態(tài)分配設備號

int register_chrdev_region(dev_t first, unsigned int count, char *name);
  • first:要分配的起始設備號。first的次編號部分通常是從0開始,但不是強制的(first = MKDEV(10, 0);)
  • count:請求分配的設備號的總數。注意,如果count太大,你要求的范圍可能溢出到下一次編號;但是只要你要求的編號范圍可用,一切都任然會正確工作。
  • name:設備名字。

動態(tài)申請設備號

int alloc_chrdev_region(dev_t *dev, unsigned baseminor, unsigned count, const
char *name)
  • dev:只是一個輸出參數,保存申請到的設備號。
  • baseminor:次設備號,它常常是0;
  • count:要申請的設備號數量。
  • name:設備名字。

動態(tài)分配的缺點是你無法提前創(chuàng)建設備節(jié)點,因為分配給你的主設備號會發(fā)生變化。我們申請到了設備節(jié)點之后,可以用前面講到的宏定義 MAJOR() 來獲取主設備號。

【3】釋放主次設備號
void unregister_chrdev_region(dev_t from, unsigned count)
  • from:要釋放的設備號。
  • count:表示從 from 開始,要釋放的設備號數量。

(2)定義file_operations結構體-初始化接口函數

file_operations 就是把系統(tǒng)調用和驅動函數關聯(lián)起來的關鍵數據結構。這個結構的每一個成員都對應著一個系統(tǒng)調用,相應的系統(tǒng)調用將讀取 file_operations 中相應的函數指針,接著把控制權轉交給函數,從而完成了Linux設備驅動程序工作。在系統(tǒng)內部,I/O設備的存取操作通過特定的入口點來進行,而這組特定的入口點恰恰是由設備驅動提供的。通常這組設備驅動程序接口是由結構 file_operations結構體向系統(tǒng)說明的,它定義在include/linux/fs.h中。傳統(tǒng)上,一個 file_operations 結構或者其一個指針稱為fops(或者它的一些變體),結構中的每個成員必須指向驅動中的函數,這些函數實現一個特別的操作,或者對于不支持的操作留置為NULL。當指定為NULL指針時內核的確切的行為是每個函數不同的。

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 (*read_iter) (struct kiocb *, struct iov_iter *);
    ssize_t (*write_iter) (struct kiocb *, struct iov_iter *);
    int (*iopoll)(struct kiocb *kiocb, bool spin);
    int (*iterate) (struct file *, struct dir_context *);
    int (*iterate_shared) (struct file *, struct dir_context *);
    __poll_t (*poll) (struct file *, struct poll_table_struct *); 
    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 *);
    unsigned long mmap_supported_flags; 
    int (*open) (struct inode *, struct file *);
    int (*flush) (struct file *, fl_owner_t id);
    int (*release) (struct inode *, struct file *);
    int (*fsync) (struct file *, loff_t, loff_t, int datasync);
    int (*fasync) (int, struct file *, int);
    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);
...

我就拿后面我們要寫的代碼為例講解一下,我們在file_operations中將open系統(tǒng)調用函數指向了chrtest_drv_open這個函數,open系統(tǒng)調用就會把控制權轉交給這個函數,完成驅動函數與系統(tǒng)調用函數的轉換。

static int chrtest_drv_open (struct inode *node, struct file *file)
{
	printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);
	return 0;
}
/* 定義自己的file_operations結構體*/
static struct file_operations chrtest_fops = {
	.owner = THIS_MODULE,
	.open = chrtest_drv_open,
};

(3)分配cdev結構體與注銷

內核在內部使用類型struct cdev的結構體來代表字符設備。在內核調用你的設備操作之前,你必須分配一個這樣的結構體并注冊給linux內核,在這個結構體里有對于這個設備進行操作的函數,具體定義在file_operations結構體中。該結構體定義在include/linux/cdev.h文件中。

struct cdev {
	struct kobject kobj;
	struct module *owner;
	const struct file_operations *ops;
	struct list_head list;
	dev_t dev;
	unsigned int count;
};

獲取cdev:struct cdev *cdev_alloc(void)

注銷cdev:

void cdev_del(struct cdev *p)

(4)綁定主次設備號,fops到cdev中,注冊cdev給Linux內核

在分配到cdev結構體后,接下來我們將它初始化,并將對該設備驅動所支持的系統(tǒng)調用函數存放在
file_operations結構體添加進來,然后我們通過cdev_add函數將他們注冊給Linux內核,這樣完成整個
Linux設備的注冊過程。
初始化設備:cdev_init(struct cdev *dev, struct file_operations *fops);
cdev_add的函數原型如下:

int cdev_add(struct cdev *dev, dev_t num, unsigned int count);
  • dev是cdev結構。
  • num是這個設備相應的第一個設備號。
  • count是應當關聯(lián)到設備的設備號的數目。

內核通過一個散列表 (哈希表) 來記錄設備編號。哈希表由數組和鏈表組成,吸收數組查找快,鏈表增刪效率高,容易拓展等優(yōu)點。以主設備號為 cdev_map 編號,使用哈希函數 f(major)=major%255 來計算組數下標 (使用哈希函數是為了鏈表節(jié)點盡量平均分布在各個數組元素中,提高查詢效率);主設備號沖突, 則以次設備號為比較值來排序鏈表節(jié)點。如下圖所示,內核用 struct cdev 結構體來描述一個字符設備,并通過struct kobj_map 類型的散列表 cdev_map 來管理當前系統(tǒng)中的所有字符設備。

cdev_add 函數用于向內核的 cdev_map 散列表添加一個新的字符設備。
linux字符設備驅動程序編寫流程,# IGKBoard(imx6ull)驅動開發(fā),驅動開發(fā),linux,嵌入式硬件,c語言,物聯(lián)網

例子:

static struct file_operations chrtest_fods ={
	.owner = THIS_MODULE,
	.open = chrtest_open,
};

	chrtest_cdev = cdev_alloc();/*獲取cdev*/
	chrtest_cdev->owner = THIS_MODULE; /*.owner這表示誰擁有你這個驅動程序*/
	cdev_init(chrtest_cdev, &chrtest_fops); /*將fops到cdev中*/
	result = cdev_add(chrtest_cdev, devno, 1); /*將字符設備注冊進內核*/
	if(0 != result)
	{
		printk(KERN_INFO " %s driver can't register cdev:result=%d\n", DEV_NAME,
		result);
	}

(5)創(chuàng)建設備類型、注冊設備節(jié)點

【1】創(chuàng)建

手動創(chuàng)建設備節(jié)點

輸入如下命令創(chuàng)建/dev/chardev 這個設備節(jié)點文件:

mknod /dev/chardev c 10 0

在/dev路徑下創(chuàng)建一個名字為chardev的字符設備節(jié)點,主設備號為10,次設備號為0。

當我們使用上述命令,創(chuàng)建了一個字符設備文件時,實際上就是創(chuàng)建了一個設備節(jié)點 inode 結構體,并且將該設備的設備編號記錄在成員 i_rdev,將成員 f_op 指針指向了 def_chr_fops 結構體。這就是 mknod 負責的工作內容。

linux字符設備驅動程序編寫流程,# IGKBoard(imx6ull)驅動開發(fā),驅動開發(fā),linux,嵌入式硬件,c語言,物聯(lián)網
mknod 命令最終執(zhí)行 init_special_inode 函數:

void init_special_inode(struct inode *inode, umode_t mode, dev_t rdev)
{
	inode->i_mode = mode;
	if (S_ISCHR(mode)) 
	{
		inode->i_fop = &def_chr_fops;
		inode->i_rdev = rdev;
	} 
	else if (S_ISBLK(mode)) 
	{
		inode->i_fop = &def_blk_fops;
...

inode 上的 file_operation 并不是自己構造的 file_operation,而是字符設備通用的 def_chr_fops,那么自己構建的 file_operation 等在應用程序調用 open 函數之后,才會綁定在文件上。

自動創(chuàng)建設備節(jié)點

class_create()//創(chuàng)建設備類型,類這個概念在Linux中被抽象成一種設備的集合(/sys/class/目錄下)
device_create()//注冊設備節(jié)點(/dev/目錄下)

class_create()這個函數使用非常簡單,在內核中是一個宏定義。/include/linux/device.h中:

#define class_create(owner, name) \
({ \
static struct lock_class_key __key; \
__class_create(owner, name, &__key); \
})
  • owner:struct module結構體類型的指針,一般賦值為THIS_MODULE。
  • name:char類型的指針,類名。

device_create()用于創(chuàng)建設備:

struct device *device_create(struct class *class, struct device *parent, dev_t devt, void *drvdata, const char *fmt, ...)
  • class:該設備依附的類。
  • parent:父設備。
  • devt:設備號(此處的設備號為主次設備號)。
  • drvdata:私有數據。
  • fmt:設備名。

創(chuàng)建例子:

	/*自動創(chuàng)建設備類型、/dev設備節(jié)點*/
	chrdev_class = class_create(THIS_MODULE, DEV_NAME); /*創(chuàng)建設備類型sys/class/chrdev*/
	if (IS_ERR(chrdev_class)) {
		result = PTR_ERR(chrdev_class);
		goto ERROR;
	}
	device_create(chrdev_class, NULL, MKDEV(dev_major, 0), NULL, DEV_NAME); /*/dev/chrdev 注冊這個設備節(jié)點*/
【2】注銷

注銷設備類型:

void class_destroy(struct class *cls)

注銷設備節(jié)點:

device_destroy()

注銷例子:

device_destroy(chrdev_class, MKDEV(dev_major, 0)); /*注銷這個設備節(jié)點*/
class_destroy(chrdev_class); /*刪除這個設備類型*/

4 - 字符設備開發(fā)與測試

(1)驅動源碼與測試源碼

字符設備驅動開發(fā)源碼:

/*************************************************************************
  > File Name: char_dev.c
  > Author: WangDengtao
  > Mail: 1799055460@qq.com 
  > Created Time: 2023年03月16日 星期四 16時40分29秒
 ************************************************************************/

#include <linux/errno.h>
#include <linux/miscdevice.h>
#include <linux/kernel.h>
#include <linux/major.h>
#include <linux/mutex.h>
#include <linux/proc_fs.h>
#include <linux/seq_file.h>
#include <linux/stat.h>
#include <linux/init.h>
#include <linux/device.h>
#include <linux/tty.h>
#include <linux/kmod.h>
#include <linux/gfp.h>
#include <linux/module.h>
#include <linux/fs.h>

/*如果沒有定義DEV_MAJOR就設置設備號為0,采用動態(tài)申請,如果有則使用宏定義的設備號*/
//#define DEV_MAJOR 88
#ifndef DEV_MAJOR
#define DEV_MAJOR 0
#endif

#define DEV_NAME  "chardev"       /*宏定義設備的名字*/
#define MIN(a,b)  (a < b ? a : b)

int dev_major = DEV_MAJOR;        /*主設備號*/
static struct cdev *chrtest_cdev; /*創(chuàng)建cdev結構體*/
static char kernel_buf[1024];     
static struct class *chrdev_class; /*定義一個class用于自動創(chuàng)建類*/

/*實現對應的open/read/write等函數,填入file_operations結構體 */
static ssize_t char_read(struct file *file, char __user *buf, size_t size, loff_t *offset)
{
	int err;
	printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);
	/*將內核空間的數據復制到用戶空間*/
	err = copy_to_user(buf, kernel_buf, MIN(1024, size));
	return MIN(1024, size);
}

static ssize_t char_write (struct file *file, const char __user *buf, size_t size, loff_t *offset)
{
	int err;
	printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);
	/*將buf中的數據復制到寫緩沖區(qū)kernel_buf中,因為用戶空間內存不能直接訪問內核空間的內存*/
	err = copy_from_user(kernel_buf, buf, MIN(1024, size)); 
	return MIN(1024, size);
}

static int char_open (struct inode *node, struct file *file)
{
	printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);
	return 0;
}

static int char_close (struct inode *node, struct file *file)
{
	printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);
	return 0;
}

/*定義自己的file_operations結構體*/
static struct file_operations chrtest_fops = {
	.owner = THIS_MODULE,
	.open  = char_open,
	.read  = char_read,
	.write = char_write,
	.release = char_close,
};

/*注冊驅動函數:寫入口函數,安裝驅動程序時就會調用這個入口函數 */
static int __init chardev_init(void)
{
	int result;
	/*
	   dev_t 定義在文件 include/linux/types.h
	   typedef __u32 __kernel_dev_t;
	   ......
	   typedef __kernel_dev_t dev_t;
	   可以看出 dev_t 是__u32 類型的,而__u32 定義在文件 include/uapi/asm-generic/int-ll64.h里面,定義如下:
	   typedef unsigned int __u32;
	   綜上所述,dev_t 其實就是 unsigned int 類型,是一個 32 位的數據類型。
	   主設備號和次設備號兩部分,其中高 12 位為主設備號,低 20 位為次設備號。因此 Linux系統(tǒng)中主設備號范圍為 0~4095。
	 */
	dev_t devno;/*定義一個dev_t的變量表示設備號*/

	printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);

	/*字符設備驅動注冊的流程二:分配主次設備號,這里不僅支持靜態(tài)指定,也支持動態(tài)申請*/
	/*靜態(tài)申請主次設備號*/
	if(0 != dev_major)
	{
		devno = MKDEV(dev_major, 0);//將主設備號dev_major和從設備號0分配給devno變量
		result = register_chrdev_region(devno, 1, DEV_NAME);//請求分配一個設備號,名字為DEV_NAME(chardev),設備號是:88 0
	}
	else
	{
		result = alloc_chrdev_region(&devno, 0, 1, DEV_NAME);//求分配一個名字為chardev的設備號,從設備號為0,保存到devno變量中
		dev_major = MAJOR(devno);//獲取設備號
	}
	/*失敗后的處理結果,總規(guī)上面只執(zhí)行一次,所以直接在外面判斷就可*/
	if(result < 0)
	{
		printk(KERN_ERR " %s chardev can't use major %d\n", DEV_NAME, dev_major);
		return -ENODEV;
	}

	printk(KERN_DEBUG " %s driver use major %d\n", DEV_NAME, dev_major);

	/*字符串設備驅動流程三:分配cdev結構體,使用動態(tài)申請的方式*/
	/*
	   內核在內部使用類型struct cdev的結構體來代表字符設備。在內核調用你的設備操作之前,你必須分配
	   一個這樣的結構體并注冊給linux內核,在這個結構體里有對于這個設備進行操作的函數,具體定義在
	   file_operation結構體中。
	 */
	if(NULL == (chrtest_cdev = cdev_alloc()))
	{
		printk(KERN_ERR "%s driver can't alloc for the cdev\n", DEV_NAME);
		unregister_chrdev_region(devno, 1);//釋放掉設備號
		return -ENOMEM;
	}

	/*字符設備驅動流程四:分配cdev結構體,綁定主次設備號,fops到cdev結構體中,并且注冊到linux內核*/
	chrtest_cdev -> owner = THIS_MODULE; /*.owner這表示誰擁有這個驅動程序*/
	cdev_init(chrtest_cdev, &chrtest_fops);/*初始化設備*/
	result = cdev_add(chrtest_cdev, devno, 1); /*將字符設備注冊進內核*/
	if(0 != result)
	{
		printk(KERN_INFO "%s driver can't register cdev:result = %d\n", DEV_NAME, result);
		goto ERROR;
	}
	printk(KERN_INFO "%s driver can register cdev:result = %d\n", DEV_NAME, result);

	/*自動創(chuàng)建設備類型、/dev設備節(jié)點*/

	chrdev_class = class_create(THIS_MODULE, DEV_NAME); /*創(chuàng)建設備類型sys/class/chrdev*/
	if (IS_ERR(chrdev_class)) {
		result = PTR_ERR(chrdev_class);
		goto ERROR;
	}
	device_create(chrdev_class, NULL, MKDEV(dev_major, 0), NULL, DEV_NAME); 
	/*/dev/chrdev 注冊這個設備節(jié)點*/

	return 0;

ERROR:
	printk(KERN_ERR" %s driver installed failure.\n", DEV_NAME);
	cdev_del(chrtest_cdev);
	unregister_chrdev_region(devno, 1);
	return result;

}

/* 有入口函數就應該有出口函數:卸載驅動程序時,就會去調用這個出口函數*/
static void __exit chardev_exit(void)
{
	printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);
	/* 注銷設備類型、/dev設備節(jié)點*/

	device_destroy(chrdev_class, MKDEV(dev_major, 0)); /*注銷這個設備節(jié)點*/
	class_destroy(chrdev_class); /*刪除這個設備類型*/

	cdev_del(chrtest_cdev); /*注銷字符設備*/
	unregister_chrdev_region(MKDEV(dev_major,0), 1); /*釋放設備號*/
	printk(KERN_ERR" %s driver version 1.0.0 removed!\n", DEV_NAME);
	return;
}


/*調用函數 module_init 來聲明 xxx_init 為驅動入口函數,當加載驅動的時候 xxx_init函數就會被調用.*/
module_init(chardev_init);
/*調用函數module_exit來聲明xxx_exit為驅動出口函數,當卸載驅動的時候xxx_exit函數就會被調用.*/
module_exit(chardev_exit);

/*添加LICENSE和作者信息,是來告訴內核,該模塊帶有一個自由許可證;沒有這樣的說明,在加載模塊的時內核會“抱怨”.*/
MODULE_LICENSE("Dual BSD/GPL");//許可 GPL、GPL v2、Dual MPL/GPL、Proprietary(專有)等,沒有內核會提示.
MODULE_AUTHOR("WangDengtao");//作者
MODULE_VERSION("V1.0");//版本

測試字符設備驅動源碼:

/*************************************************************************
  > File Name: char_dev_test.c
  > Author: WangDengtao
  > Mail: 1799055460@qq.com 
  > Created Time: 2023年03月16日 星期四 16時50分29秒
 ************************************************************************/

#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdio.h>
#include <string.h>

int main(int argc, char **argv)
{
	int fd;
	char buf[1024];
	int len;
	/* 1. 判斷參數 */
	if (argc < 2)
	{
		printf("Usage: %s -w <string> /dev/??\n", argv[0]);
		printf(" %s -r\n", argv[0]);
		return -1;
	}
	/* 2. 打開文件 */
	if(argc == 4)
	{
		fd = open(argv[3], O_RDWR);
	}
	if(argc == 3)
	{
		fd = open(argv[2], O_RDWR);
	}
	if (fd == -1)
	{
		printf("can not open file %s\n", argv[3]);
		return -1;
	}
	/* 3. 寫文件或讀文件 */
	if ((0 == strcmp(argv[1], "-w")) && (argc == 4))
	{
		len = strlen(argv[2]) + 1;
		len = len < 1024 ? len : 1024;
		printf("Write to %s success!\n", argv[3]);
		printf("write len: %d\n", len);
		write(fd, argv[2], len);
	}
	else if((0 == strcmp(argv[1], "-r")) && (argc == 3))
	{
		memset(buf, 0, sizeof(buf));
		len = read(fd, buf, 1024);
		printf("Read from %s success!\n", argv[2]);
		printf("read len: %ld\n", strlen(buf)+1);
		buf[1023] = '\0';
		printf("char_dev_test read : %s\n", buf);
	}
	else
	{
		printf("Usage: %s -w <string> /dev/??\n", argv[0]);
		printf(" %s -r\n", argv[0]);
		return -1;
	}
	close(fd);
	return 0;
}

(1)x86架構虛擬機上運行

Makefile:

KERNAL_DIR ?= /lib/modules/$(shell uname -r)/build
PWD :=$(shell pwd)
obj-m := char_dev.o

CC=gcc
APP_NAME=char_dev_test

all:
	$(MAKE) -C $(KERNAL_DIR) M=$(PWD) modules
	@${CC} ${APP_NAME}.c -o ${APP_NAME}
	@make clear

clear:
	@rm -f *.o *.cmd *.mod *.mod.c
	@rm -rf *~ core .depend .tmp_versions Module.symvers modules.order -f
	@rm -f .*ko.cmd .*.o.cmd .*.o.d
	@rm -f *.unsigned

clean:
	@rm -f *.ko
	@rm -f ${APP_NAME}

運行結果:

linux字符設備驅動程序編寫流程,# IGKBoard(imx6ull)驅動開發(fā),驅動開發(fā),linux,嵌入式硬件,c語言,物聯(lián)網
linux字符設備驅動程序編寫流程,# IGKBoard(imx6ull)驅動開發(fā),驅動開發(fā),linux,嵌入式硬件,c語言,物聯(lián)網

(2)arm架構開發(fā)板上運行

Makefile:

KERNAL_DIR ?= /home/wangdengtao/imx6ull/imx6ull/bsp/kernel/linux-imx
PWD :=$(shell pwd)
obj-m := char_dev.o

CC=arm-linux-gnueabihf-gcc
APP_NAME=char_dev_test

all:
	$(MAKE) -C $(KERNAL_DIR) M=$(PWD) modules
	@${CC} ${APP_NAME}.c -o ${APP_NAME}

	@make clear


clear:
	@rm -f *.o *.cmd *.mod *.mod.c
	@rm -rf *~ core .depend .tmp_versions Module.symvers modules.order -f
	@rm -f .*ko.cmd .*.o.cmd .*.o.d
	@rm -f *.unsigned

clean:
	@rm -f *.ko
	@rm -f ${APP_NAME}

運行結果:
linux字符設備驅動程序編寫流程,# IGKBoard(imx6ull)驅動開發(fā),驅動開發(fā),linux,嵌入式硬件,c語言,物聯(lián)網
這里提醒一下,我們需要將我們的測試程序和驅動程序復制到我們的tftpboot目錄下開發(fā)板才可以進行獲取。
開發(fā)板獲取以及測試:

root@igkboard:~# tftp -gr char_dev_test 192.168.10.168
root@igkboard:~# tftp -gr char_dev.ko 192.168.10.168
root@igkboard:~# ls
char_dev.ko  char_dev_test  hello.ko
root@igkboard:~# chmod a+x char_dev_test 

root@igkboard:~# ./char_dev_test                                       
Usage: ./char_dev_test -w <string> /dev/??
./char_dev_test -r

root@igkboard:~# insmod char_dev.ko
root@igkboard:~# ls -l /dev/chardev 
crw------- 1 root root 243, 0 Mar 18 04:34 /dev/chardev

root@igkboard:~# ./char_dev_test -w hello /dev/chardev 
Write to /dev/chardev success!
write len: 6
root@igkboard:~# ./char_dev_test -r /dev/chardev 
Read from /dev/chardev success!
read len: 6
char_dev_test read : hello
root@igkboard:~# rmmod char_dev
root@igkboard:~# ls -l /dev/chardev                    
ls: cannot access '/dev/chardev': No such file or directory

測試成功。

(4)copy_to/from_user()函數

代碼中出現的兩個沒有提到的函數:

static inline long copy_to_user(void __user *to, const void *from, unsigned long n);
/*
to: 目標地址,這個地址是用戶空間的地址; 
from: 源地址,這個地址是內核空間的地址; 
n: 將要拷貝的數據的字節(jié)數。
*/
unsigned long copy_from_user (void * to, const void __user * from, unsigned long n);
/*
to: 目標地址,這個地址是內核空間的地址; 
from: 源地址,這個地址是用戶空間的地址; 
n: 將要拷貝的數據的字節(jié)數。
*/

copy_to_user 和 copy_from_user 是在進行驅動相關程序設計的時候,要經常遇到的函數。由于內
核空間與用戶空間的內存不能直接互訪,因此借助函數 copy_to_user() 完成內核空間到用戶空間的復制,函數 copy_from_user() 完成用戶空間到內核空間的復制。

我們代碼中用到的全局變量kernel_buf是保存寫進去的內容的,我們write的時候調用了copy_from_user(kernel_buf, buf, MIN(1024, size)函數,將要寫進去的數據(buf)復制到讀緩沖區(qū)(kernel_buf)中,然后再read的時候,調用copy_to_user(buf, kernel_buf, MIN(1024, size)函數將kernel_buf中的值讀取出來復制到buf中,就可以直接讀到buf中了,也就獲取到了。


5 - inode與file結構體

(1)inode結構體

Linux中一切皆文件,當我們在Linux中創(chuàng)建一個文件時,就會在相應的文件系統(tǒng)中創(chuàng)建一個inode與之對應,文件實體和文件inode是一一對應的,創(chuàng)建好一個inode會存在存儲器中。第一次open就會將inode在內存中有一個備份,同一個文件被多次打開并不會產生多個inode,當所有被打開的文件都被close之后,inode在內存中的實例才會被釋放。既然如此,當我們使用mknod(或其他方法)創(chuàng)建一個設備文件時,也會在文件系統(tǒng)中創(chuàng)建一個inode,這個inode和其他的inode一樣,用來存儲關于這個文件的靜態(tài)信息(不變的信息),包括這個設備文件對應的設備號,文件的路徑以及對應的驅動對象等。

struct inode {
	······
	struct hlist_node	i_hash;
	struct list_head	i_list;		/* backing dev IO list */
	struct list_head	i_sb_list;

	//主次設備號
	dev_t				i_rdev;

	struct list_head	i_devices;
	//用聯(lián)合體是因為該文件可能是塊設備文件或者字符設備文件
	union {
		struct pipe_inode_info	*i_pipe;	//管道文件
		struct block_device	    *i_bdev;	//塊設備文件
		struct cdev		*i_cdev;	//字符設備文件
	};
	
	//私有數據
	void			*i_private; /* fs or device private pointer */
};

我們一般比較關心的只有兩個變量:

  • dev_t i_rdev:
    代表設備文件的節(jié)點,這個成員包含實際的設備編號
  • struct cdev *i_cdev:
    這個結構體代表字符設備,這個成員包含一個指針,指向這個結構體,當節(jié)點指的是一個字符設備文件時。

(2)file結構體

file結構體代表一個打開的文件。它由內核在open時創(chuàng)建,并傳遞給在文件上操作的任何函數,直到最后的關閉。在文件的所有實例都關閉后,內核釋放這個數據結構。

struct file結構體 用來表示一個動態(tài)的設備,每當open打開一個文件時就會產生一個struct file結構體 與之對應。

struct file {
	union {
		struct list_head	fu_list;
		struct rcu_head 	fu_rcuhead;
	}f_u;
  ······
  
	const struct file_operations	*f_op;	//該文件對應的操作方法
	
	unsigned int 	f_flags;	
	fmode_t			f_mode;	//打開文件的權限,比如:只讀打開、只寫打開、讀寫打開
	loff_t			f_pos;	//文件指針的偏移量
	
	/* needed for tty driver, and maybe others */
	void			*private_data;	//私有數據
};

linux字符設備驅動程序編寫流程,# IGKBoard(imx6ull)驅動開發(fā),驅動開發(fā),linux,嵌入式硬件,c語言,物聯(lián)網
結合上面的圖片可以進一步了解兩個結構體之間是如何聯(lián)系的。文章來源地址http://www.zghlxwxcb.cn/news/detail-791375.html


到了這里,關于Linux下字符設備驅動開發(fā)以及流程介紹的文章就介紹完了。如果您還想了解更多內容,請在右上角搜索TOY模板網以前的文章或繼續(xù)瀏覽下面的相關文章,希望大家以后多多支持TOY模板網!

本文來自互聯(lián)網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。如若轉載,請注明出處: 如若內容造成侵權/違法違規(guī)/事實不符,請點擊違法舉報進行投訴反饋,一經查實,立即刪除!

領支付寶紅包贊助服務器費用

相關文章

  • 字符設備實現內部驅動原理及分步注冊流程

    字符設備實現內部驅動原理及分步注冊流程

    ?應用層:open函數回調到驅動中open操作方法的路線: open()---sys_open()---struct inode結構體---struct cdev結構體---struct file_operations結構體---mycdev_open() 1、分配對象空間 2、對象空間的初始化 3、對象的注冊 4、對象的注銷 流程模板(非詳細) open函數參數是路徑下的文件名字,根據

    2024年02月09日
    瀏覽(19)
  • Linux驅動開發(fā)基礎_在設備樹中指定中斷以及在代碼中獲得中斷

    Linux驅動開發(fā)基礎_在設備樹中指定中斷以及在代碼中獲得中斷

    目錄 1 設備樹里中斷節(jié)點的語法 1.1 設備樹里的中斷控制器 1.2?設備樹里使用中斷 2??設備樹里中斷節(jié)點的示例 3?在代碼中獲得中斷 3.1 對于 platform_device? 3.2??對于 I2C 設備、SPI 設備 3.3??調用 of_irq_get 獲得中斷號 3.4?對于 GPIO? 參考文檔:內核 Documentationdevicetreebindingsin

    2024年02月16日
    瀏覽(22)
  • 驅動開發(fā)--字符驅動設備2

    字符設備驅動 1.定義 以字節(jié)流的形式進行訪問,且只能順序訪問的設備,針對字符設備編寫的驅動叫做字符設備驅動 2.字符設備框架 用戶空間通過IO函數如open、read、write、close等函數接口,調用內核空間中的字符設備驅動函數中的用戶自定義的open、read、write、close等函數,通

    2024年02月15日
    瀏覽(49)
  • 字符設備驅動開發(fā)

    字符設備驅動開發(fā)

    1、字符設備驅動簡介 字符設備是 Linux 驅動中最基本的一類設備驅動, 字符設備就是一個一個字節(jié),按照字節(jié) 流進行讀寫操作的設備,讀寫數據是分先后順序的。 比如我們最常見的點燈、按鍵、IIC、SPI, LCD 等等都是字符設備,這些設備的驅動就叫做字符設備驅動。 先來簡

    2024年02月10日
    瀏覽(29)
  • 嵌入式Linux(8):字符設備驅動--注冊字符類設備

    雜項設備 注冊雜項設備: 注銷雜項設備: 字符類設備 文件:include/linux/cdev.h 步驟流程: 定義一個cdev結構體。 使用cdev_init函數初始化cdev結構體成員變量。 參數: 第一個:要初始化的cdev結構體 第二個:文件操作集: cdev-ops = fops;//實際就是把文件操作集寫ops 使用cdev_add函數

    2023年04月22日
    瀏覽(24)
  • Linux設備驅動——第三章字符驅動

    當對幸福的憧憬過于急切,那痛苦就在人的心靈深處升起?!涌?本章的目的是編寫一個完整的字符設備驅動。我們開發(fā)一個字符驅動是因為這一類適合大部分簡單的硬件設備。字符驅動也比塊驅動易于理解。本章的最終目的是編寫一個模塊化的字符驅動,但是我們不會在

    2024年02月08日
    瀏覽(24)
  • Linux 驅動學習筆記 ——(1)字符設備驅動

    Linux 驅動學習筆記 ——(1)字符設備驅動

    《【正點原子】I.MX6U嵌入式Linux驅動開發(fā)指南》學習筆記 字符設備是 Linux 驅動中最基本的一類設備驅動,字節(jié)設備就是按照字節(jié)流來讀寫的設備,常見的字符設備包括:LED、蜂鳴器、按鍵、I2C 以及 SPI 等。 Linux 中一切皆文件,字符設備驅動加載成功后會在 /dev 目錄下生成相

    2024年02月08日
    瀏覽(26)
  • 字符設備驅動開發(fā)(最初方式)

    字符設備驅動開發(fā)(最初方式)

    字符設備是Linux中最基本的一類設備驅動,我們常見的點燈、按鍵、IIC、SPI、LCD等等都是通過字符設備驅動框架來進行開發(fā)的。字符設備驅動是通過一個一個字節(jié)流的方式來進行讀寫操作設備,讀寫數據是分先后順序的。 通過空間劃分的方式來說,Linux系統(tǒng)中分為用戶空間和

    2024年02月17日
    瀏覽(24)
  • Linux 驅動之字符設備

    Linux 驅動之字符設備

    什么是設備號 Linux 規(guī)定每一個字符設備或者塊設備都必須有一個專屬的設備號。一個設備號由主設備號和次設備號組成。主設備號用來表示某一類驅動,如鼠標,鍵盤都可以歸類到 USB 驅動中。而次設備號是用來表示這個驅動下的各個設備。比如第幾個鼠標,第幾個鍵盤等。

    2024年02月16日
    瀏覽(21)
  • 驅動開發(fā) 字符設備驅動分部注冊實現LED燈

    驅動開發(fā) 字符設備驅動分部注冊實現LED燈

    head.h 驅動文件 應用文件 現象實現

    2024年02月19日
    瀏覽(18)

覺得文章有用就打賞一下文章作者

支付寶掃一掃打賞

博客贊助

微信掃一掃打賞

請作者喝杯咖啡吧~博客贊助

支付寶掃一掃領取紅包,優(yōu)惠每天領

二維碼1

領取紅包

二維碼2

領紅包