Linux設備驅動開發---字符設備驅動程序

字符設備驅動程序

  • 1 主設備和次設備的概念
    • 設備號的注冊和釋放
      • 靜態方法
      • 動態方法
      • 區別
  • 2 設備文件操作
    • struct file_operations與struct file、struct inode關系
  • 3 分配和注冊字符設備
    • class_create
    • cdev_add
    • device_create
  • 4 字符設備驅動程序

字符設備通過字符(一個接一個的字符)以流方式向用戶程序傳遞數據,就像串行端口那樣。字符設備驅動通過/dev目錄下的特殊文件公開設備的屬性和功能,通過這個文件可以在設備和用戶應用程序之間交換數據,也可以通過它來控制實際的物理設備。這也是Linux的基本概念,一切皆文件。字符設備驅動程序是內核源碼中最基本的設備驅動程序。字符設備在內核中表示為struct cdev的實例,struct cdev定義在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; /* 隸屬于同一主設備號的次設備號的個數.*/
} __randomize_layout;

1 主設備和次設備的概念

字符設備在/dev目錄下,使用 ls -l命令查看
在這里插入圖片描述

開頭為c的代表字符設備文件,開頭為b的代表塊設備文件,日期左邊的第五列、第六列用<X,Y>格式表示,X代表的是主設備號,Y代表的次設備號,這是典型的從用戶空間標識字符設備,及其主次設備號的方法。

內核用dev_t類型變量維持設備號,該變量是u32。主設備號僅占12位,次設備號占20位。

typedef __kernel_dev_t		dev_t;typedef __u32 __kernel_dev_t;typedef unsigned int __u32;

dev_t類型定義在include/linux/kdev_t.h中,可以通過如下兩個宏定義來獲取主、次設備號:

MAJOR(dev_t dev);
MINOR(dev_t dev);

如果有主設備和次設備號,也可以通過宏MKDEV(int major,int minor)來構建dev_t。

設備注冊時,必須使用主設備號和次設備號,前者標識一個特定的驅動程序,后者用作標識使用該驅動程序的各設備(設備列表中的數組索引),因為同一個驅動可處理多個設備,而不同的驅動程序可以處理相同類型的不同設備。

設備號的注冊和釋放

設備號在系統范圍內標識設備文件,有兩種不同的方法分配設備號。
下面兩個函數都在fs/char_dev.c實現

靜態方法

靜態方法是調用register_chardev_region()函數,該方法必須事先知道所需的設備號

int register_chrdev_region(dev_t from, unsigned count, const char *name)

這個函數成功返回0,失敗返回錯誤碼。from是由我們所需的主設備號和合理范圍內的次設備號組成,可由MKDEV構建。count是所需的連續設備號數目,name是相關設備或者驅動程序的名字。

動態方法

使用alloc_chardev_region()函數,使內核自動分配設備號,建議采用這種方法獲得有效的設備號。

int alloc_chrdev_region(dev_t *dev, unsigned baseminor, unsigned count,const char *name)

這個函數成功返回0,失敗返回錯誤碼。dev獲取分配的設備號,baseminor代表申請的次設備號范圍內的第一個數字,count代表次設備的數目,name代表相關設備或者驅動程序的名字。

區別

這兩種分配方法的區別在于,第一種方法必須事先知道所需的設備號,這就是注冊制:把所需的設備號告訴內核。這可能在教學中使用,只有自己使用該驅動程序時,才會這樣選擇,如果在其他機器上加載該驅動程序,就無法保證所選擇的設備號在這臺機器未被占用,這會引起設備號的沖突和麻煩。第二種更安全,因為內核幫助獲取一個合適的設備號,所以我們甚至不需要關心在其他機器上加載該模塊所出現的問題,內核將根據具體情況來自動分配。

2 設備文件操作

可以在文件上執行的操作取決于管理文件的設備驅動程序。這樣的操作在內核中定義為struct file_operations的實例。struct file_operations定義了一組回調函數,用于處理文件上的所有用戶空間的系統調用。舉個例子,如果想讓用戶在設備文件上執行write操作,必須在驅動中實現write函數對于的回調函數,并把它添加到綁定在設備上的struct file_operations中,struct file_operations定義在include/linux/fs.h中。

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 (*iterate) (struct file *, struct dir_context *);int (*iterate_shared) (struct file *, struct dir_context *);unsigned int (*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 *);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);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 **, void **);long (*fallocate)(struct file *file, int mode, loff_t offset,loff_t len);void (*show_fdinfo)(struct seq_file *m, struct file *f);
#ifndef CONFIG_MMUunsigned (*mmap_capabilities)(struct file *);
#endifssize_t (*copy_file_range)(struct file *, loff_t, struct file *,loff_t, size_t, unsigned int);int (*clone_file_range)(struct file *, loff_t, struct file *, loff_t,u64);ssize_t (*dedupe_file_range)(struct file *, u64, u64, struct file *,u64);
};

其中的每一個函數都和系統調用鏈接在一起,它們都不是必需的。當用戶代碼在指定文件上調用與文件相關的系統調用時,內核會查找負責這個文件的驅動程序,定位它的struct file_operations結構,并檢查和該系統調用匹配的方法是否已經定義。如果已經定義了,就運行它。如果未定義,則根據系統調用不同返回不同的錯誤碼。

struct file_operations與struct file、struct inode關系

struct inode表示一個具體文件。一個設備或者驅動會由struct inode的實例表示。在該結構體中,我們需要注意以下幾個域。

struct inode {...const struct file_operations	*i_fop;	/* former ->i_op->default_file_ops */union {struct pipe_inode_info	*i_pipe;	/* 如果是Linux管道,則設置并使用 */struct block_device	*i_bdev;		/* 如果是塊設備,則設置并使用 */struct cdev		*i_cdev;			/* 如果是字符設備,則設置并使用 */char			*i_link;unsigned		i_dir_seq;};
....
};

struct inode里面也有struct file_operations,但是i_fop指向的是默認的索引節點操作,如果struct inode代表的是字符設備,則i_cdev會指向一個struct cdev結構,對文件進行操作時,使用的是cdev中file_operations中定義的文件操作方法。

struct file代表的是一個進程打開的文件,其里面也有struct file_operations

struct file {...const struct file_operations	*f_op;...
};

當我們在應用層使用open函數打開一個文件時,會創建struct file對象,初始化struct file對象時,struct file對象中的file_operations將指向struct inode的file_operations(準備的來說,struct inode如果沒有定義文件的具體操作,將指向默認的file_operations,如果定義了,比如字符設備,將指向字符設備的file_operations)

比如我們使用open打開兩個字符設備

fd0 = open("/dev/com0",O_RDWR);fd1 = open("/dev/com1",O_RDWR);

如下圖
請添加圖片描述
struct inode使用struct cdev中的file_operations,struct file也指向struct cdev中的file_operations,當對com0或者com1操作時,直接調用struct file的file_operations。

如何將file_operations里面定義的操作和struct cdev結構綁定到一起呢?我們可以使用cdev_init函數,將struct cdev中的ops指向第二個參數指向的內容

void cdev_init(struct cdev *, const struct file_operations *)

3 分配和注冊字符設備

字符設備在內核中表示為struct cdev的實例。在編寫字符設備驅動程序時,目標是最終創建并注冊與struct file_operations關聯的結構實例,為用戶空間提供一組可以在該設備上執行的操作函數,為了實現這個目標,必須執行以下幾個步驟:

  1. 使用alloc_chardev_region()保留一個主設備號和一定范圍的次設備號。
  2. 使用class_create()創建自己的設備類。
  3. 創建一個struct file_operations(傳遞給cdev_init),每一個設備都需要創建,并調用call_init和cdev_add注冊這個設備。
  4. 調用device_create()創建每個設備,并給它們一個合適的名字,這樣,就可以在/dev目錄下創建出設備。

class_create

宏class_create()用于動態創建設備的邏輯類,并完成部分字段的初始化,然后將其添加進Linux內核系統中。此函數的執行效果就是在目錄/sys/class下創建一個新的文件夾,此文件夾的名字為此函數的第二個輸入參數,但此文件夾是空的。宏class_create()在實現時,調用了函數__class_create(),作用和函數__class_create()基本相同。
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結構體類型的指針,指向函數__class_create()即將創建的struct class類型對象的擁有者,一般賦值為THIS_MODULE,此結構體的詳細定義見文件include/linux/module.h。
  • 參數name是char類型的指針,代表即將創建的struct class變量的名字,用于給struct class的name字段賦值。

返回值為創建的邏輯類。

此宏需要與函數class_destroy()配對使用,不能單獨使用,當單獨使用時,第一次不會出現錯誤,但當第二次插入模塊時就會出現錯誤。

cdev_add

函數cdev_add()用于向Linux內核系統中添加一個新的cdev結構體變量所描述的字符設備,并且使這個設備立即可用。
在文件linux/cdev.h中定義:

int cdev_add(struct cdev *, dev_t, unsigned)

函數 cdev_add()有三個輸入參數,第一個輸入參數代表即將被添加入Linux內核系統的字符設備;第二個輸入參數是dev_t類型的變量,此變量代表設備的設備號,其中包括主設備號和次設備號;第三個輸入參數是無符號的整型變量,代表想注冊設備的設備號的范圍,用于給struct cdev中的字段count賦值

device_create

函數device_create()用于動態地創建邏輯設備,并對新的邏輯設備類進行相應的初始化,將其與此函數的第一個參數所代表的邏輯類關聯起來,然后將此邏輯設備加到Linux內核系統的設備驅動程序模型中。函數能夠自動地在/sys/devices/virtual目錄下創建新的邏輯設備目錄,在/dev目錄下創建與邏輯類對應的設備文件。

該函數定義在linux/device.h中

struct device *device_create(struct class *cls, struct device *parent, dev_t devt, void *drvdata, const char *fmt, ...);
  • 函數device_create()的第一個輸入參數代表與即將創建的邏輯設備相關的邏輯類,也就是class_create

  • 第二個輸入參數代表即將創建的邏輯設備的父設備的指針,子設備與父設備的關系是:當父設備不可用時,子設備不可用,子設備依賴父設備,父設備不依賴子設備。

  • 第三個輸入參數是邏輯設備的設備號

  • 第四個輸入參數是void類型的指針,代表回調函數的輸入參數。

  • 第五個輸入參數是邏輯設備的設備名,即在目錄/sys/devices/virtual創建的邏輯設備目錄的目錄名。

返回值是struct device結構體類型的指針,指向新創建的邏輯設備,

device_create創建了設備文件,我們就可以根據該設備文件來和驅動或設備交互了。

注意:函數device_create()必須和函數device_destroy()配對使用,這樣才不會出現錯誤

4 字符設備驅動程序

#include <linux/module.h>
#include <linux/init.h>
#include <linux/fs.h>
#include <linux/version.h>
#include <linux/device.h>
#include <linux/cdev.h>static unsigned int major; /* major number for device */
static struct class *dummy_class;
static struct cdev dummy_cdev;int dummy_open(struct inode * inode, struct file * filp)
{pr_info("Someone tried to open me\n");return 0;
}int dummy_release(struct inode * inode, struct file * filp)
{pr_info("Someone closed me\n");return 0;
}ssize_t dummy_read (struct file *filp, char __user * buf, size_t count,loff_t * offset)
{pr_info("Nothing to read guy\n");return 0;
}ssize_t dummy_write(struct file * filp, const char __user * buf, size_t count,loff_t * offset)
{pr_info("Can't accept any data guy\n");return count;
}struct file_operations dummy_fops = {open:       dummy_open,release:    dummy_release,read:       dummy_read,write:      dummy_write,
};static int __init dummy_char_init_module(void)
{struct device *dummy_device;int error;dev_t devt = 0;/* Get a range of minor numbers (starting with 0) to work with */error = alloc_chrdev_region(&devt, 0, 1, "dummy_char");if (error < 0) {pr_err("Can't get major number\n");return error;}major = MAJOR(devt);pr_info("dummy_char major number = %d\n",major);/* Create device class, visible in /sys/class */dummy_class = class_create(THIS_MODULE, "dummy_char_class");if (IS_ERR(dummy_class)) {pr_err("Error creating dummy char class.\n");unregister_chrdev_region(MKDEV(major, 0), 1);return PTR_ERR(dummy_class);}/* Initialize the char device and tie a file_operations to it */cdev_init(&dummy_cdev, &dummy_fops);dummy_cdev.owner = THIS_MODULE;/* Now make the device live for the users to access */cdev_add(&dummy_cdev, devt, 1);dummy_device = device_create(dummy_class,NULL,   /* no parent device */devt,    /* associated dev_t */NULL,   /* no additional data */"dummy_char");  /* device name */if (IS_ERR(dummy_device)) {pr_err("Error creating dummy char device.\n");class_destroy(dummy_class);unregister_chrdev_region(devt, 1);return -1;}pr_info("dummy char module loaded\n");return 0;
}static void __exit dummy_char_cleanup_module(void)
{unregister_chrdev_region(MKDEV(major, 0), 1);device_destroy(dummy_class, MKDEV(major, 0));cdev_del(&dummy_cdev);class_destroy(dummy_class);pr_info("dummy char module Unloaded\n");
}module_init(dummy_char_init_module);
module_exit(dummy_char_cleanup_module);MODULE_AUTHOR("John Madieu <john.madieu@gmail.com>");
MODULE_DESCRIPTION("Dummy character driver");
MODULE_LICENSE("GPL");

在/dev目錄下創建了字符設備

在這里插入圖片描述
在/sys/class下創建了目錄

在這里插入圖片描述
設備的詳細信息
在這里插入圖片描述

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/379574.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/379574.shtml
英文地址,請注明出處:http://en.pswp.cn/news/379574.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

Java LinkedHashMap getOrDefault()方法與示例

LinkedHashMap類的getOrDefault()方法 (LinkedHashMap Class getOrDefault() method) getOrDefault() method is available in java.util package. getOrDefault()方法在java.util包中可用。 getOrDefault() method is used to get the value associated with the given key el…

Java中的異常棧軌跡和異常鏈

Java中允許對異常進行再次拋出&#xff0c;以提交給上一層進行處理&#xff0c;最為明顯的例子為Java的常規異常。 常規異常&#xff1a;有Java所定義的異常&#xff0c;不需要異常聲明&#xff0c;在未被try-catch的情況下&#xff0c;會被默認上報到main()方法。 Example: pu…

貪心算法---背包問題(物品可以分割問題)

問題背景&#xff1a; 有一天&#xff0c;阿里巴巴趕著一頭毛驢上山砍柴。砍好柴準備下山時&#xff0c;遠處突然出現一股煙塵&#xff0c;彌漫著直向上空飛揚&#xff0c;朝他這兒卷過來&#xff0c;而且越來越近。靠近以后&#xff0c;他才看清原來是一支馬隊&#xff0c;他…

同步---信號量

信號量1 信號量2 驅動程序和測試程序3 內核的具體實現總結1 信號量 Linux中的信號量是一種睡眠鎖。如果有一個任務試圖獲得一個已經被占用的信號量時&#xff0c;信號量會將其放到一個等待隊列&#xff0c;然后讓其睡眠&#xff0c;這時處理器去執行其他代碼。當持有信號量的進…

Java Float類floatToIntBits()方法與示例

Float類floatToIntBits()方法 (Float class floatToIntBits() method) floatToIntBits() method is available in java.lang package. floatToIntBits()方法在java.lang包中可用。 floatToIntBits() method follows IEEE 754 floating-point standards and according to standa…

解釋三度帶和六度帶的概念以及各坐標系如何定義

★ 地形圖坐標系&#xff1a;我國的地形圖采用高斯&#xff0d;克呂格平面直角坐標系。在該坐標系中&#xff0c;橫軸&#xff1a;赤道&#xff0c;用&#xff39;表示&#xff1b;縱軸&#xff1a;中央經線&#xff0c;用&#xff38;表示&#xff1b;坐標原點&#xff1a;中央…

0-1背包問題(物品不可分割)

問題背景&#xff1a; 所謂“鐘點秘書”&#xff0c;是指年輕白領女性利用工余時間為客戶提供秘書服務&#xff0c;并按鐘點收取酬金。“鐘點秘書”為客戶提供有償服務的方式一般是&#xff1a;采用電話、電傳、上網等“遙控”式 服務&#xff0c;或親自到客戶公司處理部分業務…

算法---KMP算法

字符串1 KMP算法狀態機概述構建狀態轉移1 KMP算法 原文鏈接&#xff1a;https://zhuanlan.zhihu.com/p/83334559 先約定&#xff0c;本文用pat表示模式串&#xff0c;長度為M&#xff0c;txt表示文本串&#xff0c;長度為N&#xff0c;KMP算法是在txt中查找子串pat&#xff0…

cache初接觸,并利用了DataView

我們在寫代碼的時候,如果數據控件要獲得數據,一般方法,Conn.Open();OleDbCommand cmd;cmd new OleDbCommand(sql, Conn);GridView1.DataSource dbcenter.accessGetDataSet(sql);GridView1.DataBind();Conn.close();但如果多個數據控件要綁定數據,則比較頻繁打開數據庫,效率一…

Java ByteArrayInputStream reset()方法及示例

ByteArrayInputStream類reset()方法 (ByteArrayInputStream Class reset() method) reset() method is available in java.util package. reset()方法在java.util包中可用。 reset() method is used to reset this ByteArrayInputStream to the last time marked position and …

回文數猜想

問題描述&#xff1a; 一個正整數&#xff0c;如果從左向右讀&#xff08;稱之為正序數&#xff09;和從右向左讀&#xff08;稱之為倒序數&#xff09;是一樣的&#xff0c;這樣的數就叫回文數。任取一個正整數&#xff0c;如果不是回文數&#xff0c;將該數與他的倒序數相加…

文件上傳 帶進度條(多種風格)

文件上傳 帶進度條 多種風格 非常漂亮&#xff01; 友好的提示 以及上傳驗證&#xff01; 部分代碼&#xff1a; <form id"form1" runat"server"><asp:ScriptManager ID"scriptManager" runat"server" EnablePageMethods&quo…

同步---自旋鎖

1 自旋鎖的基本概念 自旋鎖最多只能被一個可執行線程持有&#xff0c;如果一個執行線程試圖獲得一個已經被使用的自旋鎖&#xff0c;那么該線程就會一直進行自旋&#xff0c;等待鎖重新可用。在任何時刻&#xff0c;自旋鎖都可以防止多余一個的執行線程同時進入臨界區。 Linu…

實習日志----4.播放時段參數設置

由于客戶在下發廣告時&#xff0c;一則廣告可在多個時段播放&#xff0c;這就需要設置多個播放時段的參數。 但在這種情況下&#xff0c;我并不知道用戶每次需要下發幾個時段&#xff0c;所以前臺不能設定死。 因此我要實現這么一個功能&#xff0c;讓用戶根據自己的需要來動態…

線性插值算法實現圖像_C程序實現插值搜索算法

線性插值算法實現圖像Problem: 問題&#xff1a; We are given an array arr[] with n elements and an element x to be searched amongst the elements of the array. 給定一個數組arr []&#xff0c;其中包含n個元素和一個要在該數組的元素中搜索的元素x 。 Solution: 解&…

hdu 1197

地址&#xff1a;http://acm.hdu.edu.cn/showproblem.php?pid1197 題意&#xff1a;求一個數轉換成10&#xff0c;12&#xff0c;16進制后各個位上的數的和是否相等。 mark&#xff1a;模擬進制轉換。 代碼&#xff1a; #include <stdio.h>int zh(int a, int n) {int su…

linux系統編程---線程總結

線程總結1 線程的實現線程創建線程退出線程等待線程清理2 線程的屬性線程的分離線程的棧地址線程棧大小線程的調度策略線程優先級3 線程的同步互斥鎖讀寫鎖條件變量信號量線程是系統獨立調度和分配的基本單位。同一進程中的多個線程將共享該進程中的全部系統資源&#xff0c;例…

博客上一些項目相關源碼鏈接

GitHub&#xff1a;https://github.com/beyondyanyu/Sayingyy

重新開啟Ctrl+Alt+Backspace快捷鍵

UBUNTU老用戶知道CtrlAltBackspace這個快捷鍵是用來快速重啟X的在9.04中被默認關閉了&#xff0c;那如何來打開它呢&#xff1f;在終端中輸入&#xff1a;sudo gedit /etc/X11/xorg.conf在其中加入&#xff1a;Section “ServerFlags”Option “DontZap” “false”EndSection退…

Java LocalDate類| 帶示例的getDayOfYear()方法

LocalDate類的getDayOfYear()方法 (LocalDate Class getDayOfYear() method) getDayOfYear() method is available in java.time package. getDayOfYear()方法在java.time包中可用。 getDayOfYear() method is used to get the day-of-year field value of this LocalDate obje…