驅動開發_2.字符設備驅動

目錄

    • 1. 什么是字符設備
    • 2. 設備號
      • 2.1 設備號概念
      • 2.2 通過設備號dev分別獲取主、次設備號的宏函數
      • 2.3 主設備號的申請
        • 靜態申請
        • 動態分配
      • 2.4 注銷設備號
    • 3. 字符設備
      • 3.1 注冊字符設備
      • 3.2 注銷字符設備
      • 3.3 應用程序和驅動程序的關系
      • 3.4 file_opertaions結構體
      • 3.5 class_create
      • 3.6 創建設備文件的2種方法
    • 4. 字符設備驅動程序的開發步驟
    • 5. 應用層與驅動間拷貝數據
      • 5.1 copy_to_user
      • 5.2 copy_to_user
    • 6. 驅動中字符設備文件的相關3個結構體
    • 7. 例子
      • 7.1 手動創建和自動創建的區別
      • 7.2 手動創建
      • 7.3 自動創建

1. 什么是字符設備

字符設備是Linux三大設備之一(另外兩種是塊設備,網絡設備),字符設備就是字節流形式通訊的I/O設備, 在讀寫時是按字節順序進行的,絕大部分設備都是字符設備,常見的字符設備包括鼠標、鍵盤、顯示器、串口等等都是字符設備,這些設備的驅動就叫做字符設備驅動。

2. 設備號

2.1 設備號概念

為方便管理, Linux中每個設備都有一個設備號,設備號由主設備號和次設備號兩部分組成:
主設備號:用來標識某種設備類型。
次設備號:用來區分同一種設備類型的不同設備。
Linux使用一個結構描述設備號:dev_t 高12位是主設備號,低20位是次設備號

2.2 通過設備號dev分別獲取主、次設備號的宏函數

關于設備號操作的 3 個宏函數。這三個宏是 Linux 內核(通常在 <linux/kdev_t.h> 中定義)中用于操作設備號 dev_t 的核心工具。

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

功能

MAJOR(dev):獲取設備號 dev 中的主設備號(通過右移去除次設備號位)。
MINOR(dev):獲取設備號 dev 中的次設備號(通過與掩碼屏蔽掉主設備號位)。
MKDEV(ma, mi):將主設備號 ma 和次設備號 mi 組合成一個完整的設備號(通過左移主設備號后與次設備號合并)。

2.3 主設備號的申請

靜態申請

在內核源碼路徑下確定一個沒有使用的主設備號(可查看Documentation/devices.txt文檔確定未使用的設備號),通過函數 register_chrdev_region 或register_chrdev注冊設備號:
1. register_chrdev_region
int register_chrdev_region(dev_t from, unsigned count, const char *name);
參數

from:要分配的起始設備編號,from的次設備編號部分常常是 0, 但是沒有強制要求
count:請求的連續設備編號的總數
name:設備的名稱,它會出現在 /proc/devices 和sysfs 中

返回值

成功返回0,否則返回小于0

2. register_chrdev
int register_chrdev(unsigned int major, const char *name, const struct file_operations *fops);
參數

major:major為0時,此函數將動態分配一個主設備號,并返回它的值,若major>0時,此函數將嘗試使用給定的設備號數值保留該設備號
name:設備的名稱,它會出現在/proc/devices和sysfs中
fops:struct file_operations結構體,為應用程序提供的訪問本驅動的操作方法指針

返回值

成功返回0,否則返回小于0。
動態分配

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

dev :設備號,動態分配,若調用成功用此參數返回分配的設備號>0。
baseminor:起始的次設備號
count:需要分配的設備號數目
name:設備名(體現在/proc/devices中)

返回值

成功返回0,否則返回非0。

2.4 注銷設備號

void unregister_chrdev_region(dev_t from, unsigned count);
釋放從 from 開始的 count 個設備號

3. 字符設備

3.1 注冊字符設備

在linux內核中用struct cdev來表示一個字符設備:

struct cdev {struct kobject kobj;	//內核中的最頂層的基類struct module *owner;	//表示該字符設備屬于哪個模塊,一般值設置為THIS_MODULE。const struct file_operations *ops;	 //注冊驅動的關鍵, 要填充成這個結構體變量struct list_head list;	//內核鏈表, 內核用來統一管理字符設備dev_t dev;			//設備號(包括主設備號和次設備號)unsigned int count;	//次設備號個數
};

在編寫字符驅動之前,需要先注冊一個字符設備,示例如下:

struct cdev {struct kobject kobj;struct module * owner;			//值為THIS_MODULE, 代表當前模塊const struct file_operations;	//應用程序的訪問接口, 驅動要負責實體此結構體中的函數struct list_head list;dev_t dev	//設備號unsigned int cout; //次設備號的數據
};
//自行查找 內核鏈表的實現
在編寫字符驅動之前, 必須注冊一個字符設備//定義一個cdev對象
struct cdev test_cdev;//動態分配一個cdev對象
struct cdev *cdev_alloc(void);//字符設備文件操作函數集合
struct file_operations test_fops = {.owner = THIS_MODULE,.open = xxx_open,.read = xxx_open,.write = xxx_write,......
};/*初始化cdev結構體成員*/
test_cdev.owner = THIS_MODULE;
cdev_init(&test_cdev, &test_fops);/*向內核中添加字符設備*/
cdev_add(&test_cdev, dev_number, dev_count);

3.2 注銷字符設備

void cdev_del(struct cdev *pcdev);
釋放cdev結構體空間,cdev_del函數內部能夠判斷struct cdev定義的對象是用的堆內存還是棧內存還是數據段內存的。這個函數cdev_del調用時,會先去檢查有沒有使用堆內存,如果有用到的話,會先去釋放掉所占用的堆內存,然后在注銷掉這個設備驅動。所以,如果struct cdev要用堆內存一定要用內核提供的這個cdev_alloc去分配堆內存,因為內部會做記錄,這樣在cdev_del注銷掉這個驅動的時候,才會去釋放掉那段堆內存。

3.3 應用程序和驅動程序的關系

3.4 file_opertaions結構體

關于 C庫以及如何通過系統調用調入到內核空間不用去管,重點關注的是應用程序和具體的驅動,應用程序使用到的函數在具體驅動程序中都有與之對應的函數,比如應用程序中調用了 open這個函數,那么在驅動程序中也得有一個名為 open的函數。每一個系統調用,在驅動中都有與之對應的一個驅動函數,在 Linux內核文件 include/linux/fs.h中有個叫做file_operations的結構體,此結構體就是Linux內核驅動操作函數集合,內容如下所示:

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 *);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 (*aio_fsync) (struct kiocb *, 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 **);long (*fallocate)(struct file *file, int mode, loff_t offset,loff_t len);int (*show_fdinfo)(struct seq_file *m, struct file *f);/* get_lower_file is for stackable file system */struct file* (*get_lower_file)(struct file *f);
};

file_operations 結構體函數成員太多,幾個常用的:

llseek() 函數用于修改文件指針偏移量
read() 函數用于讀取設備文件
write() 函數用于向設備文件寫入數據
poll() 函數用于輪詢監聽設備狀態
unlocked_ioctl() 函數提供對于設備的控制功能,與應用程序中的 ioctl 函數對應
compat_ioctl() 函數與 unlocked_ioctl() 函數功能一樣,區別在于在 64 位系統上,32 位的應用程序調用將會使用此函數。在 32 位的系統上運行 32 位的應用程序調用的是unlocked_ioctl()
mmap() 函數用于將設備的內存映射到進程空間中(也就是用戶空間)
open() 函數用于打開設備文件
release() 函數用于釋放(關閉)設備文件,與應用程序中的 close 函數對應
fasync() 函數用于刷新待處理的數據,用于將緩沖區中的數據刷新到磁盤中
aio_fsync() 函數與 fasync 函數的功能類似,只是 aio_fsync 是異步刷新待處理的數據

3.5 class_create

宏class_create()用于動態創建設備的邏輯類,并完成部分字段的初始化,然后將其添加進Linux內核系統中。此函數的執行效果就是在/sys/class/目錄下創建一個新的文件夾,此文件夾的名字為此函數的第二個輸入參數,但此文件夾是空的。宏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字段賦值。
通俗地說,就是指向struct class名稱的字符串的指針。

返回值
與函數__class_create()的返回值相同,都代表新創建的邏輯類。

3.6 創建設備文件的2種方法

1.使用mknod命令手動創建
命令格式:
mknod devname type major minor

drvname:設備文件名
type:設備文件類型 c字符設備/d塊設備
major:主設備號
minor:次設備號

2.自動創建(加載模塊時注冊創建,卸載模塊時釋放)
1)函數register_chrdev自動獲取注冊一個字符設備并申請設備號,同時注冊file_operations操作方法集
2)函數class_create創建一個struct class類,這個類存放在sysfs下面,一旦創建好了這個類, 調用device_create函數在/dev目錄下創建相應的設備節點。這樣加載驅動程序模塊時,用戶空間中udev會自動響應device_create函數,去/sysfs下尋找對用的類從而創建設備節點。
3)函數device_create返回值是創建一個struct device型設備關聯設備號,class,和設備節點。

4. 字符設備驅動程序的開發步驟

在內核模塊中使用一個結構來描述字符設備對象:

struct cdev {struct kobject kobj;  // 文件系統相關,由系統來管理,不需要自己添加struct module *owner;  // 用于模塊計數const struct file_operations *ops; // 對硬件的操作方法集struct list_head list;  // 用于管理字符設備的鏈表dev_t dev;   // 設備號;把軟件和硬件結合起來;主設備號<<20 +次設備號unsigned int count; //  同一主設備號下的,次設備號的個數
};

4.1 注冊字符設備

  1. 定義和分配struct cdev
  2. 初始化字符設備對象(對硬件操作的方法集):cdev_init(struct cdev *, const struct file_operations *)
  3. 向內核申請設備號:register_chrdev_region(dev_t from, unsigned count, const char * name) 或者 alloc_chrdev_region()
  4. 添加當前的字符設備到內核中:cdev_add(struct cdev *, dev_t, unsigned)

4.2 實現 struct file_operations函數,此函數向應用程序提供調用接口。

4.3 卸載字符設備

  1. cdev_del(struct cdev *); // 刪除字符設備
  2. unregister_chrdev_region(dev_t from, unsigned count); //刪除

5. 應用層與驅動間拷貝數據

用戶訪問內核時,進程需要從用戶態切換為內核態,然后再訪問內核,這么做的目的是防止用戶隨意篡改內核數據。在編寫某個外設的驅動時,需要實現內核中的 read 和 write 函數,此時站在內核的角度,無法直接讀取用戶緩沖區的數據也無法直接向用戶緩沖區寫入數據。因此,就需要用到copy_to_usercopy_from_user函數。
copy_to_user():完成內核空間到用戶空間的復制
copy_from_user():完成用戶空間到內核空間的復制
#include <linux/uaccess.h> // copy_to_user & copy_from_user 需要包含的頭文件

5.1 copy_to_user

函數聲明
調用該函數需站在內核的角度,即調用該函數所在文件中的緩沖區,都屬于內核緩沖區
ulong copy_to_user(void __user * to, const void * from, unsigned long n);

第一個參數 to:要向用戶空間返回數據的地址
第二個參數 from: 內核空間地址,將此地址處的數據拷貝給用戶空間的to指針指向的地址。
第三個參數 n:要寫入數據到用戶空間中的字節數

返回值:成功返回 0
具體使用

static char readbuf[100]; /* 讀緩沖區 *//** 該函數一般用于內核中的 read 函數的定義中,將內核接收到的內容拷貝到用戶緩沖區*/static ssize_t xxx_read(struct file *filp, char __user *buf,size_t cnt, loff_t *offt)
{int ret = copy_to_user(buf, readbuf, cnt);   // 將readbuf中的內容拷貝到buf// 拷貝的字節數大小為cntif(ret == 0){printk("kernel send data ok!\n");}else{printk("kernel send data failed!\n"); } return 0;
}

5.2 copy_to_user

6. 驅動中字符設備文件的相關3個結構體

struct inode: 記錄文件的物理信息。
struct file:代表一個打開的文件,由內核打開時創建,關閉時釋放。
struct file_operations: 函數指針集合,是對硬件操作的方法集,通過在驅動程序中重新實現并注冊到內核中,供應用層調用。

7. 例子

hello_cdev.c

#include <linux/module.h>   // 模塊相關頭文件
#include <linux/init.h>     // 模塊初始化和退出宏
#include <linux/kernel.h>   // 內核核心功能
#include <linux/cdev.h>     // 字符設備結構
#include <linux/fs.h>       // 文件系統接口#define BUF_LEN     32             // 設備緩沖區大小
#define HELLO_CDEV_NAME "hello_cdev" // 設備名稱static dev_t devno;                // 設備號變量(包含主/次設備號)
struct cdev hello_cdev;            // 字符設備結構體
static char gbuffer[BUF_LEN] = "HelloWorld"; // 設備數據緩沖區
static int curlen = 0;             // 當前有效數據長度/* 設備打開函數 */
static int hello_cdev_open(struct inode * inodep, struct file * filp)
{printk("%s\n", __func__);      // 打印函數名(調試用)return 0;                      // 成功打開
}/* 設備關閉函數 */
static int hello_cdev_release(struct inode * inodep, struct file * filp)
{printk("%s\n", __func__);      // 打印函數名(調試用)return 0;                      // 成功關閉
}/* 設備讀取函數 */
static ssize_t hello_cdev_read(struct file * filp, char __user * buf, size_t size, loff_t * ppos)
{int ret;size_t copy_len = size;/* 防止讀取超出實際數據范圍 */if(copy_len > curlen) {copy_len = curlen;}/* 將內核數據復制到用戶空間 */ret = copy_to_user(buf, gbuffer, copy_len);if(ret) {  // 復制失敗處理printk("%s copy_to_user failed\n", __func__);return -EFAULT;  // 返回錯誤碼(錯誤地址)}return copy_len;  // 返回實際讀取字節數
}/* 設備寫入函數 */
static ssize_t hello_cdev_write(struct file * filp, const char __user * buf, size_t size, loff_t * ppos)
{int ret;size_t copy_len = size;/* 限制寫入不超過緩沖區大小 */if(copy_len > BUF_LEN) {copy_len = BUF_LEN;}/* 將用戶數據復制到內核空間 */ret = copy_from_user(gbuffer, buf, copy_len);if(ret) {  // 復制失敗處理printk("%s copy_from_user failed\n", __func__);return -EFAULT;  // 返回錯誤碼(錯誤地址)}curlen = copy_len;  // 更新有效數據長度return copy_len;    // 返回實際寫入字節數
}/* 設備操作函數集 */
static struct file_operations hello_ops = {.owner = THIS_MODULE,          // 指向當前模塊.open = hello_cdev_open,       // 打開設備回調.release = hello_cdev_release, // 關閉設備回調.read = hello_cdev_read,       // 讀設備回調.write = hello_cdev_write,     // 寫設備回調
};/* 模塊初始化函數 */
static int __init hello_cdev_init(void)
{int ret;/* 1. 動態申請設備號 */ret = alloc_chrdev_region(&devno, 0, 1, HELLO_CDEV_NAME);if(ret) {printk(KERN_ERR "%s alloc_chrdev_region failed\n", __func__);return ret;  // 返回錯誤碼}printk("%s major:%d, minor:%d\n", __func__, MAJOR(devno), MINOR(devno));  // 打印主/次設備號/* 2. 初始化字符設備 */hello_cdev.owner = THIS_MODULE;cdev_init(&hello_cdev, &hello_ops);  // 關聯操作函數集/* 3. 添加設備到系統 */ret = cdev_add(&hello_cdev, devno, 1);if(ret) {printk(KERN_ERR "%s cdev_add failed\n", __func__);goto cdev_err;  // 跳轉到錯誤處理}curlen = strlen(gbuffer);  // 初始化有效數據長度return 0;  // 初始化成功cdev_err:/* 錯誤處理:釋放設備號 */unregister_chrdev_region(devno, 1);return ret;
}/* 模塊退出函數 */
static void __exit hello_cdev_exit(void)
{/* 1. 從系統移除字符設備 */cdev_del(&hello_cdev);/* 2. 釋放設備號資源 */unregister_chrdev_region(devno, 1);printk("Bye... %s\n", __func__);  // 退出提示
}/* 模塊注冊 */
module_init(hello_cdev_init);  // 指定初始化函數
module_exit(hello_cdev_exit);  // 指定退出函數
MODULE_LICENSE("GPL");         // 聲明GPL許可證

簡潔版


#include <linux/module.h>
#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/cdev.h>
#include <linux/fs.h>#define BUF_LEN     32
#define HELLO_CDEV_NAME "hello_cdev"static dev_t devno;
struct cdev hello_cdev;
static char gbuffer[BUF_LEN] = "HelloWorld";
static int curlen = 0;static int hello_cdev_open(struct inode * inodep, struct file * filp)
{printk("%s\n", __func__);return 0;
}static int hello_cdev_release(struct inode * inodep, struct file * filp)
{printk("%s\n", __func__);return 0;
}static ssize_t hello_cdev_read(struct file * filp, char __user * buf, size_t size, loff_t * ppos)
{int ret;size_t copy_len = size;/*若用戶要讀取的數據量超過了gbuffer中實際的數據量,則以實際 gbuffer中存放的數據量為準*/if(copy_len > curlen) {copy_len = curlen;}/*將內核中的數據返回給用戶空間*/ret = copy_to_user(buf, gbuffer, copy_len);if(ret) {printk("%s copy_to_user failed\n", __func__);return -EFAULT;}return copy_len;
}static ssize_t hello_cdev_write(struct file * filp, const char __user * buf, size_t size, loff_t * ppos)
{int ret;size_t copy_len = size;if(copy_len > BUF_LEN) {copy_len = BUF_LEN;}/*將用戶空間傳入的數據寫入到gbuffer中*/ret = copy_from_user(gbuffer, buf, copy_len);if(ret) {printk("%s copy_from_user failed\n", __func__);return -EFAULT;}curlen = copy_len;return copy_len;
}/*2, 實現struct file_operations中的函數*/
static struct file_operations hello_ops = {.owner = THIS_MODULE,.open = hello_cdev_open,        //用戶空間調用open API函數打開設備文件時.release = hello_cdev_release,  //用戶空間調用close API函數關閉設備文件時.read = hello_cdev_read,        //用戶空間調用read API函數讀取設備文件時.write = hello_cdev_write,      //用戶空間調用write API函數寫入設備文件時
};static int __init hello_cdev_init(void)
{/*1, 申請設備號2, 實現struct file_operations中的函數3, 初始化cdev, 并將cdev對象注冊到內核中*/int ret;/*1, 申請設備號*/ret = alloc_chrdev_region(&devno, 0, 1, HELLO_CDEV_NAME);//devno設備號,次設備號,設備號數目,設備名if(ret) {//成功返回0,不成功非0printk(KERN_ERR "%s alloc_chrdev_region failed\n", __func__);}printk("%s major:%d, minor:%d\n", __func__, MAJOR(devno), MINOR(devno));/*3, 初始化cdev, 添加cdev對象到內核中*/hello_cdev.owner = THIS_MODULE;cdev_init(&hello_cdev, &hello_ops);ret = cdev_add(&hello_cdev, devno, 1);if(ret) {printk(KERN_ERR "%s cdev_add failed\n", __func__);goto cdev_err;}curlen = strlen(gbuffer);return 0;
cdev_err:/*退出時釋放設備號*/unregister_chrdev_region(devno, 1);return ret;
}static void __exit hello_cdev_exit(void)
{/*1, 注銷cdev 對象*/cdev_del(&hello_cdev);/*2, 釋放設備號*/unregister_chrdev_region(devno, 1);printk("Bye... %s\n", __func__);
}module_init(hello_cdev_init);
module_exit(hello_cdev_exit);
MODULE_LICENSE("GPL");

7.1 手動創建和自動創建的區別

在這里插入圖片描述

7.2 手動創建

hello_cdev.c

#include <linux/module.h>
#include <linux/init.h>
#include <linux/cdev.h>
#include <linux/fs.h>#define BUF_LEN              64
#define HELLO_CDEV_NAME     "hello_cdev"
static dev_t devno;
static struct cdev  hello_cdev;
static char kbuffer[BUF_LEN] = "HelloWorld";static int hello_cdev_open(struct inode * inodep, struct file * filp)
{printk("%s\n", __func__);return 0;
}static int hello_cdev_release(struct inode * inodep, struct file * filp)
{printk("%s\n", __func__);return 0;
}static ssize_t hello_cdev_read(struct file * filp, char __user * user_buf, size_t count, loff_t * f_ops)
{int ret = 0;ssize_t copy_len = 0;int buflen = strlen(kbuffer);printk("%s\n", __func__);/*若用戶空間要讀取的數據量超過了buffer中的字符數, 則以buffer中的實際數據量為準*/copy_len = count < buflen ? count : buflen;ret = copy_to_user(user_buf, kbuffer, copy_len);if(ret) {printk(KERN_ERR "%s copy_to_user failed, copy_len:%ld\n", __func__, copy_len);return -ENOSPC;}return copy_len;
}static ssize_t hello_cdev_write(struct file * filp, const char __user * user_buf, size_t count, loff_t * f_ops)
{int ret = 0;ssize_t copy_len = count;int buflen = sizeof(kbuffer);printk("%s\n", __func__);if(copy_len > buflen) {copy_len = buflen;}/*將內核中的數據返回給用戶程序*/ret = copy_from_user(kbuffer, user_buf, copy_len);if(ret) {printk(KERN_ERR "%s copy_from_user failed, copy_len:%ld\n", __func__, copy_len);return -ENOSPC;}return copy_len;
}static struct file_operations hello_fops = {.owner = THIS_MODULE,.open = hello_cdev_open,.release = hello_cdev_release,.read = hello_cdev_read,.write = hello_cdev_write,
};static int __init hello_cdev_init(void)
{/*1, 申請設備號2, 初始化cdev對象3, 添加cdev對象到內核中4, 實現 struct file_operations函數集*//*1, 申請設備號*/int ret = 0;ret = alloc_chrdev_region(&devno, 0, 1, HELLO_CDEV_NAME);if(ret) {/*返回值 不為0, 申請 設備號失敗, 退出內核模塊的加載流程*/printk(KERN_ERR "%s, alloc_chrdev_region failed\n", __func__);return -EFAULT;}printk("%s, major:%d , minor:%d\n", __func__, MAJOR(devno), MINOR(devno));/*2, 初始化 cdev */hello_cdev.owner = THIS_MODULE;/*關聯cdev 和 file_operations*/cdev_init(&hello_cdev, &hello_fops);ret = cdev_add(&hello_cdev, devno, 1);if(ret) {printk(KERN_ERR "%s, cdev_add filed\n", __func__);ret = -EFAULT;goto cdev_err;}return 0;
cdev_err:unregister_chrdev_region(devno, 1);return ret;
}static void __exit hello_cdev_exit(void)
{/*注銷字符設備*/cdev_del(&hello_cdev);/*釋放設備號*/unregister_chrdev_region(devno, 1);printk("Bye %s\n", __func__);
}module_init(hello_cdev_init);
module_exit(hello_cdev_exit);MODULE_LICENSE("GPL");

test_hello.c

#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>
#include <string.h>
#include <sys/types.h>
#include <stdlib.h>#define DEV_NAME    "/dev/hello-dev"
int main()
{int fd, ret;char buf[32] = {0};fd = open(DEV_NAME, O_RDWR);if(fd < 0) {perror("open");return -1;}//lseek(fd, 1, SEEK_SET);ret = read(fd, buf, sizeof(buf));    printf("read 1 count:%d, data:%s\n", ret, buf);memset(buf, 0, sizeof(buf));sprintf(buf, "%s", "This is a test");ret = write(fd, buf, strlen(buf));ret = read(fd, buf, sizeof(buf));printf("read 2 count:%d, data:%s\n", ret, buf);close(fd);return 0;
}

Makefile

APP=test_hello
#如果$(KERNELRELEASE)是空的
ifeq ($(KERNELRELEASE),)
#獲得內核源碼根目錄KERNELDIR ?= /lib/modules/$(shell uname -r)/build#獲取當前路徑	PWD:=$(shell pwd)
modules:
# $(MAKE) -C $(KERNELDIR) 解釋:  $(MAKE) 就是 make , -C 進入一個子目錄 /lib/modules/5.15.0-139-generic/build/arch, 再執行子目錄的Makefile
# M=$(PWD) 解釋: M是一個變量,值為當前目錄, 將當前M的值傳遞給 $(KERNELDIR) 目錄下的Makefile
# modules 是執行$(KERNELDIR) 目錄下的Makefile時傳遞的目標$(MAKE) -C $(KERNELDIR) M=$(PWD) modulesgcc $(APP).c -o $(APP)
elseobj-m=hello_cdev.o
endifclean:rm -rf *.o *.mod.c *.mod.o *.ko *.symvers *.order *.a *.mod .*.cmd .*.*.d $(APP)

終端

1. 進入目錄下
2. sudo dmesg -C
3. make
4. insmod hello_cdev.ko
5. dmesg查看主設備號cdev_t
6. ※mknod /dev/hello_cdev c cdev_t(上條命令查看的) 0
7. sudo chmod 666 /dev/hello_cdev
8. ./test_hello(可以在另一個終端上測)
9. 輸入測試
10.dmesg 

7.3 自動創建

hello_cdev.c

#include <linux/module.h>
#include <linux/init.h>
#include <linux/cdev.h>
#include <linux/fs.h>#define BUF_LEN              64
#define HELLO_CDEV_NAME     "hello_cdev"
static dev_t devno;
static struct cdev  hello_cdev;
static char kbuffer[BUF_LEN] = "HelloWorld";static struct class * clsp;
static struct device * devp;static int hello_cdev_open(struct inode * inodep, struct file * filp)
{printk("%s\n", __func__);return 0;
}static int hello_cdev_release(struct inode * inodep, struct file * filp)
{printk("%s\n", __func__);return 0;
}static ssize_t hello_cdev_read(struct file * filp, char __user * user_buf, size_t count, loff_t * f_ops)
{int ret = 0;ssize_t copy_len = 0;int buflen = strlen(kbuffer);printk("%s\n", __func__);/*若用戶空間要讀取的數據量超過了buffer中的字符數, 則以buffer中的實際數據量為準*/copy_len = count < buflen ? count : buflen;ret = copy_to_user(user_buf, kbuffer, copy_len);if(ret) {printk(KERN_ERR "%s copy_to_user failed, copy_len:%ld\n", __func__, copy_len);return -ENOSPC;}return copy_len;
}static ssize_t hello_cdev_write(struct file * filp, const char __user * user_buf, size_t count, loff_t * f_ops)
{int ret = 0;ssize_t copy_len = count;int buflen = sizeof(kbuffer);printk("%s\n", __func__);if(copy_len > buflen) {copy_len = buflen;}/*將內核中的數據返回給用戶程序*/ret = copy_from_user(kbuffer, user_buf, copy_len);if(ret) {printk(KERN_ERR "%s copy_from_user failed, copy_len:%ld\n", __func__, copy_len);return -ENOSPC;}return copy_len;
}static struct file_operations hello_fops = {.owner = THIS_MODULE,.open = hello_cdev_open,.release = hello_cdev_release,.read = hello_cdev_read,.write = hello_cdev_write,
};static int __init hello_cdev_init(void)
{/*1, 申請設備號2, 初始化cdev對象3, 添加cdev對象到內核中4, 實現 struct file_operations函數集*//*1, 申請設備號*/int ret = 0;ret = alloc_chrdev_region(&devno, 0, 1, HELLO_CDEV_NAME);if(ret) {/*返回值 不為0, 申請 設備號失敗, 退出內核模塊的加載流程*/printk(KERN_ERR "%s, alloc_chrdev_region failed\n", __func__);return -EFAULT;}printk("%s, major:%d , minor:%d\n", __func__, MAJOR(devno), MINOR(devno));/*2, 初始化 cdev */hello_cdev.owner = THIS_MODULE;/*關聯cdev 和 file_operations*/cdev_init(&hello_cdev, &hello_fops);ret = cdev_add(&hello_cdev, devno, 1);if(ret) {printk(KERN_ERR "%s, cdev_add filed\n", __func__);ret = -EFAULT;goto cdev_err;}/*創建設備節點*//*1, 創建設備類*/clsp = class_create(THIS_MODULE, "hello_cls");if(IS_ERR(clsp)) {ret = PTR_ERR(clsp);    /*獲得錯誤碼*/printk("%s, class_create failed\n", __func__);goto cls_err;}/*2, 創建設備文件*/devp = device_create(clsp, NULL, devno, NULL, "hello_autnode_dev");if(IS_ERR(devp)) {ret = PTR_ERR(devp);    /*獲得錯誤碼*/printk("%s, device_create failed\n", __func__);goto dev_err;}return 0;
dev_err:class_destroy(clsp);
cls_err:cdev_del(&hello_cdev);
cdev_err:unregister_chrdev_region(devno, 1);return ret;
}static void __exit hello_cdev_exit(void)
{/*1, 刪除設備文件*/device_del(devp);/*2, 注銷設備類*/class_destroy(clsp);/*3, 注銷字符設備*/cdev_del(&hello_cdev);/*4, 釋放設備號*/unregister_chrdev_region(devno, 1);printk("Bye %s\n", __func__);
}module_init(hello_cdev_init);
module_exit(hello_cdev_exit);MODULE_LICENSE("GPL");

test_hello.c

#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>
#include <string.h>
#include <sys/types.h>
#include <stdlib.h>#define DEV_NAME    "/dev/hello_autnode_dev"
int main()
{int fd, ret;char buf[32] = {0};fd = open(DEV_NAME, O_RDWR);if(fd < 0) {perror("open");return -1;}//lseek(fd, 1, SEEK_SET);ret = read(fd, buf, sizeof(buf));    printf("read 1 count:%d, data:%s\n", ret, buf);memset(buf, 0, sizeof(buf));sprintf(buf, "%s", "This is a test");ret = write(fd, buf, strlen(buf));ret = read(fd, buf, sizeof(buf));printf("read 2 count:%d, data:%s\n", ret, buf);close(fd);return 0;
}

Makefile

APP=test_hello
#如果$(KERNELRELEASE)是空的
ifeq ($(KERNELRELEASE),)
#獲得內核源碼根目錄KERNELDIR ?= /lib/modules/$(shell uname -r)/build#獲取當前路徑	PWD:=$(shell pwd)
modules:
# $(MAKE) -C $(KERNELDIR) 解釋:  $(MAKE) 就是 make , -C 進入一個子目錄 /lib/modules/5.15.0-139-generic/build/arch, 再執行子目錄的Makefile
# M=$(PWD) 解釋: M是一個變量,值為當前目錄, 將當前M的值傳遞給 $(KERNELDIR) 目錄下的Makefile
# modules 是執行$(KERNELDIR) 目錄下的Makefile時傳遞的目標$(MAKE) -C $(KERNELDIR) M=$(PWD) modulesgcc $(APP).c -o $(APP)
elseobj-m=hello_cdev.o
endifclean:rm -rf *.o *.mod.c *.mod.o *.ko *.symvers *.order *.a *.mod .*.cmd .*.*.d $(APP)

終端

1. 進入目錄
2. make 生成內核文件
3. sudo dmesg -C
4. insmod hello_cdev.ko
5. dmesg
6. ./test_hello.c (可以再開一個終端)
7. 輸入測試
8. dmesg查看
9. rmmod helloc_cdev

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

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

相關文章

直播推流技術底層邏輯詳解與私有化實現方案-以rmtp rtc hls為例-優雅草卓伊凡

直播推流技術底層邏輯詳解與私有化實現方案-以rmtp rtc hls為例-優雅草卓伊凡由于我們的甲方客戶要開始為我們項目產品上加入私有化的直播&#xff0c;這塊不得不又撿起來曾經我們做直播推流的事情了&#xff0c;其實私有化直播一直并不是一件容易的事情&#xff0c;現在大部分…

一文讀懂現代卷積神經網絡—深度卷積神經網絡(AlexNet)

目錄 深度卷積神經網絡&#xff08;AlexNet&#xff09;是什么&#xff1f; 一、AlexNet 的核心創新 1. 深度架構 2. ReLU 激活函數 3. 數據增強 4. Dropout 正則化 5. GPU 并行計算 6. 局部響應歸一化&#xff08;LRN&#xff09; 二、AlexNet 的網絡結構 三、AlexN…

JVM 垃圾收集算法全面解析

1. 引言1.1 為什么需要垃圾收集&#xff1f;在Java應用中&#xff0c;垃圾收集&#xff08;Garbage Collection&#xff0c;GC&#xff09;是一個至關重要的機制&#xff0c;它使得開發者不需要手動管理內存。與傳統的語言&#xff08;如C或C&#xff09;不同&#xff0c;Java通…

Vmware中安裝的CentOS7如何擴展硬盤大小

起初創建虛擬機時&#xff0c;大小設置不合理&#xff0c;導致我在嘗試開源項目時空間不足重新擴展硬盤&#xff0c;不僅需要在虛擬機設置中配置&#xff0c;還需要在系統內重新進行分區一、虛擬機設置打開虛擬機設置→硬盤→擴展&#xff0c;將大小設置為自己期望的大小&#…

Python+MongoDB高效開發組合

如大家所知&#xff0c;Python與MongoDB的結合是一種高效的開發組合&#xff0c;主要用于通過Python進行數據存儲、查詢及管理&#xff0c;利用MongoDB的文檔型數據庫特性實現靈活的數據處理。下面讓 Python 連接上 MongoDB&#xff1a;安裝 PyMongo&#xff1a;pip3 install p…

【論文閱讀】Masked Autoencoders Are Effective Tokenizers for Diffusion Models

introduce什么樣的 latent 空間更適合用于擴散模型&#xff1f;作者發現&#xff1a;相比傳統的 VAE&#xff0c;結構良好、判別性強的 latent 空間才是 diffusion 成功的關鍵。研究動機&#xff1a;什么才是“好的 latent 表征”&#xff1f;背景&#xff1a;Diffusion Models…

每日一SQL 【游戲玩法分析 IV】

文章目錄問題案例執行順序使用分組解決問題 案例 執行順序 SQL 語句的執行順序&#xff08;核心步驟&#xff09; 同一層級的select查詢內部, 別名在整個 SELECT 計算完成前不生效 使用分組解決 select distinct s.product_id, Product.product_name from Sales sleft join …

內部文件審計:企業文件服務器審計對網絡安全提升有哪些幫助?

企業文件服務器審計工作不僅對提升企業網絡信息安全起到重要作用&#xff0c;還能對企業內部網絡文件信息是否合規進行判斷。因此企業文件服務器審計一直被高度重視。 一、文件服務器為何成為攻擊焦點&#xff1f; 企業文件服務器通常集中存儲財務報表、人事檔案、研發資料、客…

FusionOne HCI 23 超融合實施手冊(超聚變超融合)

產品介紹 FusionOne HCI作為實現企業信息一體化的IT基礎設施平臺&#xff0c;以“軟硬件垂直深度集成和調優”、“快速部署”、“統一管理”的理念&#xff0c;提供應用融合部署&#xff0c;提升核心業務運作效率&#xff0c;降低整體采購成本。 FusionOne HCI代表了IT產品的…

AI算姻緣測算小工具流量主微信小程序開源

功能特點 響應式設計&#xff1a;完美適配各種移動設備屏幕尺寸 精美UI界面&#xff1a; 柔和的粉紅色漸變背景 圓角卡片設計 精心設計的字體和間距 愛心圖標點綴 動態效果&#xff1a; 點擊按鈕時的動畫反饋 測算結果的平滑過渡動畫 愛心漂浮動畫 進度條動態填充 AI測算功能&a…

Vue獲取上傳Excel文件內容并展示在表格中

一、安裝依賴 npm install xlsx 二、引用依賴 import XLSX from xlsx 三、代碼實現 1、注意&#xff1a;函數 analysis 中reader.readAsBinaryString(file)&#xff0c;file的數據格式如圖所示 2、示例代碼 <!-- 項目使用的前端框架為非流行框架&#xff0c;主要關注…

pipelineJob和pipeline的關系

pipelineJob與pipeline在Jenkins體系中構成配置層與執行層的協同關系,具體關聯如下: 一、核心功能定位 概念作用實現層級pipelineJob定義Job的元數據(如SCM配置、日志策略)配置層pipeline描述實際構建流程(如階段劃分、并行任務)執行層scriptPath橋梁作用:將配置層定義…

第二十篇 Word文檔自動化:Python批量生成、模板填充與內容修改,告別繁瑣排版!

python實現word 自動化重復性文檔制作&#xff0c;手動填充模板&#xff0c;效率低下還易錯1.python-docx入門&#xff1a;Word文檔的“瑞士軍刀”&#xff01;1.1 安裝與基礎概念&#xff1a;文檔、段落、運行、表格1.2 打開/創建Word文檔&#xff1a;Python與Word的初次接觸1…

【C# in .NET】7. 探秘結構體:值類型的典型代表

探秘結構體&#xff1a;值類型的典型代表 在 C# 的類型系統中&#xff0c;結構體&#xff08;Struct&#xff09;作為值類型的典型代表&#xff0c;一直扮演著既基礎又微妙的角色。許多開發者在日常編碼中雖頻繁使用結構體&#xff08;如int、DateTime等&#xff09;&#xff0…

深入探討Hadoop YARN Federation:架構設計與實踐應用

Hadoop YARN Federation簡介基本概念與設計初衷Hadoop YARN Federation作為Apache Hadoop 3.x版本的核心特性之一&#xff0c;其本質是通過多集群聯合管理機制突破單點資源管理器的性能瓶頸。傳統YARN架構中&#xff0c;單個ResourceManager&#xff08;RM&#xff09;需要管理…

STM32固件升級設計——SD卡升級固件

目錄 概述 一、功能描述 1、BootLoader部分&#xff1a; 2、APP部分&#xff1a; 二、BootLoader程序制作 1、分區定義 2、 主函數 3、SD卡升級文件檢測和更新 4、程序跳轉 三、APP程序制作 四、工程配置&#xff08;默認KEIL5&#xff09; 五、運行測試 結束語…

基于Python的圖像文字識別系統

主要語言&#xff1a;Python數據庫&#xff1a;SQLiteUI界面&#xff1a;PYQT5文字識別模型&#xff1a;Tesseract OCR&#xff08;本地搭建&#xff09;主要功能&#xff1a;登錄注冊&#xff1a;登錄注冊功能。圖片管理&#xff1a;單張/多張上傳、圖片列表、預覽、刪除、切換…

028_分布式部署架構

028_分布式部署架構 概述 本文檔介紹如何設計和實現Claude應用的分布式部署架構&#xff0c;包括負載均衡、緩存策略、服務發現、容錯機制等。 微服務架構設計 1. 服務拆分策略 from abc import ABC, abstractmethod from typing import Dict, Any, Optional import asyncio im…

duckdb和pyarrow讀寫arrow格式的方法

arrow格式被多種分析型數據引擎廣泛采用&#xff0c;如datafusion、polars。duckdb有一個arrow插件&#xff0c;原來是core插件&#xff0c;1.3版后被廢棄&#xff0c;改為社區級插件&#xff0c;名字改為nanoarrow, 別名還叫arrow。 安裝 D install arrow from community; D…

機器人位姿變換的坐標系相對性:左乘法則與右乘法則解析?

文章目錄1. 全局坐標系下機器人位姿更新的左乘法則?2. 局部坐標系下機器人位姿增量更新的右乘法則?3. 相對位姿的計算3.1. 基于世界坐標系&#xff08;全局變換&#xff09;3.2. 基于 t1t_1t1? 時刻相機的局部坐標系&#xff08;局部變換&#xff09;3.3. 兩者區別設機器人當…