在Linux系統(tǒng)中“一切皆文件”,上一篇講述了cdev結(jié)構(gòu)體就描述了一個(gè)字符設(shè)備驅(qū)動(dòng),主要包括設(shè)備號(hào)和操作函數(shù)集合。但是要怎么操作這個(gè)驅(qū)動(dòng)呢?例如,使用open()該打開誰,read()該從哪讀取數(shù)據(jù)等等。所以就需要?jiǎng)?chuàng)建一個(gè)設(shè)備文件來代表設(shè)備驅(qū)動(dòng)。
應(yīng)用程序要操縱外部硬件設(shè)備,需要像和普通文件一樣,使用open(),read(),write()(初始化cdev時(shí)實(shí)現(xiàn)的操作函數(shù))等系統(tǒng)調(diào)用來操作設(shè)備文件間接實(shí)現(xiàn)控制外部硬件設(shè)備。注冊設(shè)備驅(qū)動(dòng)后想要?jiǎng)?chuàng)建相對應(yīng)的設(shè)備文件有兩種方式:手動(dòng)創(chuàng)建和自動(dòng)創(chuàng)建。
手動(dòng)創(chuàng)建:
加載驅(qū)動(dòng)模塊之后,使用mknod命令在/dev目錄下創(chuàng)建設(shè)備文件。
mknod 設(shè)備文件路徑 文件類型 主設(shè)備號(hào) 次設(shè)備號(hào)
設(shè)備文件路徑:/dev/xxx
文件類型:c代表字符設(shè)備,b代表塊設(shè)備
設(shè)備文件代表設(shè)備驅(qū)動(dòng),用主次設(shè)備號(hào)來關(guān)聯(lián)。
自動(dòng)創(chuàng)建:
每次新添加一個(gè)驅(qū)動(dòng)都手動(dòng)創(chuàng)建感覺非常麻煩,所以比較推薦自動(dòng)創(chuàng)建設(shè)備文件。
新添加一個(gè)頭文件。
#include <linux/device.h>
創(chuàng)建設(shè)備類?
內(nèi)核中定義了struct class結(jié)構(gòu)體,一個(gè)struct class結(jié)構(gòu)體類型變量對應(yīng)一個(gè)類, 內(nèi)核同時(shí)提供了class_create函數(shù),可以用它來創(chuàng)建一個(gè)類,這個(gè)類存放于/sys/class下面。
//原型是一個(gè)宏,主要使用里面__class_create函數(shù)。
#define class_create(owner, name) \
({? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?\
?????????static struct lock_class_key __key; \
????????__class_create(owner, name, &__key); \
})
owner:類的所有者, 固定是 THIS_MODULE?
name:類名,可隨意起名struct class * __class_create(模塊所有者, 設(shè)備類名);
//參數(shù)中還有一個(gè)key,不用管和功能沒有太大的關(guān)系
//返回設(shè)備類指針
銷毀設(shè)備類
有創(chuàng)建自然有銷毀。
void class_destroy(struct class *cls);
?創(chuàng)建設(shè)備文件也叫設(shè)備節(jié)點(diǎn)
創(chuàng)建好一個(gè)設(shè)備類,調(diào)用 device_create函數(shù)就可以在/dev目錄下創(chuàng)建相應(yīng)的設(shè)備節(jié)點(diǎn)。
struct device *device_create(struct class *class, struct device *parent,
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? dev_t devt, void *drvdata, const char *fmt, ...)參數(shù)依次對應(yīng):設(shè)備類指針, 父設(shè)備指針,設(shè)備號(hào), 額外數(shù)據(jù), "設(shè)備文件名"
//父設(shè)備指針:如果有些設(shè)備之間有依賴關(guān)系,就可以傳入父設(shè)備指針,沒有就不需要
銷毀設(shè)備文件
void device_destroy(struct class *class, dev_t devt);
驅(qū)動(dòng)錯(cuò)誤處理
接下來說說怎么判斷這些函數(shù)是否創(chuàng)建成功?,有人可能覺得返回類型不都是指針嗎,直接判斷指針是不是NULL就好了。想一想如果都是返回NULL那你能判斷是哪一步出現(xiàn)了錯(cuò)誤,錯(cuò)誤的原因是什么嗎。事實(shí)上有很多函數(shù)的返回類型是指針,結(jié)果有三種分別是合法指針、NULL指針和非法指針。
那怎么判斷函數(shù)返回的指針是否為有效地址呢?使用IS_ERR宏去檢查函數(shù)的返回值,如果地址落在0xfffffffffffff000~0xffffffffffffffff范圍(64位系統(tǒng)),表示函數(shù)執(zhí)行失敗,IS_ERR宏返回真。在Linux中函數(shù)執(zhí)行錯(cuò)誤返回的非法地址對應(yīng)著一個(gè)錯(cuò)誤碼,使用PTR_ERR宏把相應(yīng)的非法地址轉(zhuǎn)換成錯(cuò)誤碼,每個(gè)錯(cuò)誤碼都用不一樣的含義,感興趣可以去errno.h中查看。
IS_ERR(指針)? ? ? ? ? ? ? ? ? ? ? //返回真,表示指針出錯(cuò)
IS_ERR_OR_NULL(指針)? ?//返回真,表示指針出錯(cuò)(可判斷空指針)
PTR_ERR(指針)? ? ? ? ? ? ? ? ? //將出錯(cuò)的指針轉(zhuǎn)換成錯(cuò)誤碼
ERR_PTR(錯(cuò)誤碼)? ? ? ? ? ? ? //將錯(cuò)誤碼轉(zhuǎn)成指針
?說了那么多我們來寫一個(gè)測試一下。
#include <linux/init.h>
#include <linux/module.h>
#include <linux/cdev.h>
#include <linux/fs.h>
#include <linux/device.h>
#include <asm/uaccess.h>
//起始次設(shè)備號(hào)
#define CDD_MINOR 0
//設(shè)備號(hào)個(gè)數(shù)
#define CDD_COUNT 1
//設(shè)備號(hào)
dev_t dev;
//聲明cdev
struct cdev cdd_cdev;
//設(shè)備類指針
struct class *cdd_class;
//設(shè)備指針
struct device *cdd_device;
int cdd_open(struct inode *inode, struct file *filp)
{
printk("enter cdd_open!\n");
return 0;
}
ssize_t cdd_read(struct file *filp, char __user *buf, size_t size, loff_t *offset)
{
printk("enter cdd_read!\n");
return 0;
}
ssize_t cdd_write(struct file *filp, const char __user *buf, size_t size, loff_t *offset)
{
printk("enter cdd_write!\n");
return 0;
}
long cdd_ioctl(struct file *filp, unsigned int cmd, unsigned long data)
{
printk("enter cdd_ioctl!\n");
return 0;
}
int cdd_release(struct inode *inode, struct file *filp)
{
printk("enter cdd_release!\n");
return 0;
}
//聲明操作函數(shù)集合
struct file_operations cdd_fops = {
.owner = THIS_MODULE,
.open = cdd_open,
.read = cdd_read,
.write = cdd_write,
.unlocked_ioctl = cdd_ioctl,//ioctl接口
.release = cdd_release,//對應(yīng)用戶close接口
};
//加載函數(shù)
int cdd_init(void)
{
int ret;
//1.動(dòng)態(tài)申請?jiān)O(shè)備號(hào)
ret = alloc_chrdev_region(&dev, CDD_MINOR, CDD_COUNT, "cdd_demo");
if(ret<0){
printk("alloc_chrdev_region failed!\n");
goto failure_register_chrdev;
}
// 2.注冊cdev
//初始化
cdev_init(&cdd_cdev, &cdd_fops);
//將cdev添加到內(nèi)核
ret = cdev_add(&cdd_cdev, dev, CDD_COUNT);
if(ret<0){
printk("cdev_add failed!\n");
goto failure_cdev_add;
}
// 3.注冊設(shè)備類
/*成功會(huì)在/sys/class目錄下出現(xiàn)cdd_class子目錄*/
cdd_class = class_create(THIS_MODULE, "cdd_class");
if(IS_ERR(cdd_class)){
printk("class_create failed!\n");
ret = PTR_ERR(cdd_class);
goto failure_class_create;
}
// 4.創(chuàng)建設(shè)備文件
cdd_device = device_create(cdd_class, NULL, dev,NULL, "cdd");
if(IS_ERR(cdd_device)){
printk("device_create failed!\n");
ret = PTR_ERR(cdd_device);
goto failure_device_create;
}
return 0;
failure_device_create:
class_destroy(cdd_class);
failure_class_create:
cdev_del(&cdd_cdev);
failure_cdev_add:
unregister_chrdev_region(dev, CDD_COUNT);
failure_register_chrdev:
return ret;
}
//卸載函數(shù)
void cdd_exit(void)
{
//銷毀設(shè)備文件
device_destroy(cdd_class, dev);
//注銷設(shè)備類
class_destroy(cdd_class);
//銷毀cdev
cdev_del(&cdd_cdev);
//注銷設(shè)備號(hào)
unregister_chrdev_region(dev, CDD_COUNT);
}
//聲明為模塊的入口和出口
module_init(cdd_init);
module_exit(cdd_exit);
MODULE_LICENSE("GPL");//GPL模塊許可證
MODULE_AUTHOR("xin");//作者
MODULE_VERSION("2.0");//版本
MODULE_DESCRIPTION("charactor driver!");//描述信息
從上圖可以看出這樣就不需要手動(dòng)創(chuàng)建設(shè)備文件了。?
這里需要說明一下我們現(xiàn)在編寫字符設(shè)備驅(qū)動(dòng)的流程是:1、注冊設(shè)備號(hào) 2、注冊 添加cdev 3、創(chuàng)建設(shè)備類 4、創(chuàng)建設(shè)備文件。如果我們在創(chuàng)建設(shè)備設(shè)備文件時(shí)出現(xiàn)了錯(cuò)誤,那我們不僅需要返回錯(cuò)誤碼,還需要把之前的設(shè)備號(hào)注銷、銷毀cdev和注銷設(shè)備類,就是哪一步出錯(cuò)了,就需要把之前的復(fù)原。這就體現(xiàn)了goto語句用來處理多步驟錯(cuò)誤處理的好處了。
注冊設(shè)備號(hào),注冊cdev合二為一
字符設(shè)備驅(qū)動(dòng)的流程有4步,內(nèi)核中提供了一個(gè)register_chrdev函數(shù)來把第一步和第二部合并。
int register_chrdev(unsigned int major, const char *name,const struct file_operations *fops);
參數(shù):
??? major - 主設(shè)備號(hào)
??? name - 設(shè)備號(hào)名字
??? fops - 操作函數(shù)集合
靜態(tài)申請成功返回0,動(dòng)態(tài)申請成功返回主設(shè)備號(hào),失敗返回負(fù)數(shù)???
注意:該函數(shù)的第一個(gè)參數(shù)不為0,就靜態(tài)申請?jiān)O(shè)備號(hào),第一個(gè)參數(shù)為0,就動(dòng)態(tài)申請?jiān)O(shè)備號(hào),該函數(shù)會(huì)自動(dòng)初始化好cdev,并添加到內(nèi)核中
該函數(shù)是調(diào)用內(nèi)核級(jí)__register_chrdev函數(shù)實(shí)現(xiàn)其功能,多添加了2個(gè)參數(shù)起始此設(shè)備號(hào)和次設(shè)備號(hào)的范圍。
static inline int register_chrdev(unsigned int major, const char *name,
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? const struct file_operations *fops)
{
? ? return __register_chrdev(major, 0, 256, name, fops);
}該函數(shù)的調(diào)用將為給定的主設(shè)備號(hào)注冊0~255作為次設(shè)備號(hào),并為每個(gè)設(shè)備建立一個(gè)對應(yīng)的默認(rèn)cdev結(jié)構(gòu)。使用這一接口的驅(qū)動(dòng)程序必須能夠處理所有256個(gè)次設(shè)備號(hào)上的open調(diào)用(無論它們是否真正對應(yīng)于實(shí)際的設(shè)備),而且不能使用大于255的主設(shè)備號(hào)和次設(shè)備號(hào)。
將使用上面方法創(chuàng)建的設(shè)備從系統(tǒng)中移除使用以下函數(shù)。
int unregister_chrdev(unsigned int major, const char *name);
major和name必須與傳遞給register_chrdev函數(shù)的值保持一致,否則該調(diào)用會(huì)失敗。
注意這種方式是早期內(nèi)核驅(qū)動(dòng)程序注冊字符設(shè)備驅(qū)動(dòng)程序的方法,現(xiàn)在不推薦使用。
訪問字符設(shè)備文件=使用字符設(shè)備驅(qū)動(dòng)?
字符設(shè)備文件都有設(shè)備號(hào),當(dāng)我們操作字符設(shè)備文件時(shí),內(nèi)核會(huì)通過設(shè)備號(hào)去找到相同設(shè)備號(hào)cdev然后把cdev中的file_operations賦值給file結(jié)構(gòu)中的file_operations,最后調(diào)用file里file_operations中我們寫好的操作函數(shù)。所以我們訪問字符設(shè)備文件就相當(dāng)于使用了字符設(shè)備驅(qū)動(dòng)。
內(nèi)核和用戶空間進(jìn)行數(shù)據(jù)交互
需要的頭文件:#include <asm/uaccess.h>
Linux中內(nèi)核空間和用戶空間是隔離的,互相之間不能直接訪問,地址空間也相互獨(dú)立。內(nèi)核中提供用戶空間到內(nèi)核空間之間數(shù)據(jù)拷貝的方法。
copy_to_user(用戶地址,內(nèi)核地址,大小)? ? ? ?// 從內(nèi)核空間--->用戶空間
copy_from_user(內(nèi)核地址,用戶地址,大小)? ?//從用戶空間--->內(nèi)核空間
//在內(nèi)核中屬于用戶空間的地址需要用 __user 修飾
注:copy_to_user和copy_from_user調(diào)用時(shí)可能導(dǎo)致睡眠,某些禁止睡眠的場合不能使用。
ioctl接口
ioctl是Linux專門為用戶層控制設(shè)備設(shè)計(jì)的系統(tǒng)調(diào)用接口,這個(gè)接口具有極大的靈活性,我們的設(shè)備打算讓用戶通過哪些命令實(shí)現(xiàn)哪些功能。
?用戶空間使用ioctl
需要的頭文件:#include <sys/ioctl.h>
int ioctl(int fd, int cmd, ...) ;
參數(shù):
fd - 文件描述符
cmd - 操作命令,代表某個(gè)動(dòng)作(由內(nèi)核定義)
...? - 不定參數(shù),可以也可以沒有,取決于內(nèi)核實(shí)現(xiàn)
返回值:執(zhí)行成功時(shí)返回 0,失敗則返回 -1 并設(shè)置全局變量 errorno 值
驅(qū)動(dòng)程序使用ioctl
需要的頭文件:#include <linux/ioctl.h>
long (*unlocked_ioctl) (struct file *filp, unsigned int cmd, unsigned long data);
ioctl 命令(cmd)的統(tǒng)一格式
將一個(gè)32位int型劃分為4個(gè)部分
設(shè)備類型 ? ?序列號(hào) ? ? 方向 ? ? ?數(shù)據(jù)尺寸
? ? ?8bit? ? ? ? ? 8bit ? ? ? ?2bit? ? ? ? ? 14bit? ? ? ?
//設(shè)備類型,可以是0~0xff之間的數(shù)稱為幻數(shù),其主要作用是使 ioctl 命令有唯一的設(shè)備標(biāo)識(shí)??
//序列號(hào),表示當(dāng)前命令是整個(gè)ioctl命令中的第幾個(gè),從0開始計(jì)數(shù)? ? ?
//方向,表示數(shù)據(jù)的傳輸方向,可以為_IOC_NONE、_IOC_READ、_IOC_WRITE、_IOC_READ | _IOC_WRITE,代表四種訪問模式:無數(shù)據(jù)、讀數(shù)據(jù)、寫數(shù)據(jù)、讀寫數(shù)據(jù)
//數(shù)據(jù)尺寸,表示涉及的用戶數(shù)據(jù)的大小
?構(gòu)造ioctl命令還是比較繁瑣的,內(nèi)核提供了宏來方便用戶構(gòu)造ioctl命令。
?_IO(設(shè)備類型,序列號(hào))? ? ? ? ? ? ? ? ? ? ? ? ?//沒有參數(shù)的命令? ? ?
_IOR(設(shè)備類型,序列號(hào),數(shù)據(jù)尺寸)? ? ? ? //該命令是從驅(qū)動(dòng)讀取數(shù)據(jù)
_IOW(設(shè)備類型,序列號(hào),數(shù)據(jù)尺寸)? ? ? ?//該命令是從驅(qū)動(dòng)寫入數(shù)據(jù)
_IOWR(設(shè)備類型,序列號(hào),數(shù)據(jù)尺寸)? ? ?//雙向數(shù)據(jù)傳輸?shù)拿?/p>
有生成命令的宏,也有拆分命令的宏。
?_IOC_DIR(cmd)? ? ? ? ? ?//從命令中提取方向
_IOC_TYPE(cmd)? ? ? ? ?//從命令中提取幻數(shù)
_IOC_NR(cmd)? ? ? ? ? ? ?//從命令中提取序數(shù)
_IOC_SIZE(cmd)? ? ? ? ? //從命令中提取數(shù)據(jù)大小
說了這么多,還是用一個(gè)簡單的示例來演示一下 ,把注冊設(shè)備號(hào),注冊cdev合二為一,內(nèi)核和用戶空間進(jìn)行數(shù)據(jù)交互,ioctl接口都用上。
用戶和內(nèi)核空間共用的頭文件,里面是ioctl命令的構(gòu)成和頭文件。
#ifndef __IOTEST_H
#define __IOTEST_H
#include <linux/ioctl.h>
//定義設(shè)備類型(幻數(shù))
#define IOC_MAGIC 'x'
#define HELLO_DEMO _IO(IOC_MAGIC,0)
#define HELLO_READ _IOR(IOC_MAGIC,1,int)
#define HELLO_WRITE _IOW(IOC_MAGIC,2,int)
#endif
驅(qū)動(dòng)模塊
#include <linux/init.h>
#include <linux/module.h>
#include <linux/cdev.h>
#include <linux/fs.h>
#include <linux/device.h>
#include <asm/uaccess.h>
#include "iotest.h"
#define CDD_MINOR 0
//設(shè)備號(hào)
dev_t dev;
//聲明cdev
struct cdev cdd_cdev;
//設(shè)備類指針
struct class *cdd_class;
//設(shè)備指針
struct device *cdd_device;
//內(nèi)核緩沖區(qū)
char arr[128] = {0};
int data = 1;
int cdd_open(struct inode *inode, struct file *filp)
{
printk("enter cdd_open!\n");
return 0;
}
//在內(nèi)核中屬于用戶空間的地址需要用 __user 修飾
ssize_t cdd_read(struct file *filp, char __user *buf, size_t size, loff_t *offset)
{
int ret;
printk("enter cdd_read!\n");
if(size>127)
size = 127;//數(shù)據(jù)不夠長,取最長的數(shù)據(jù)
ret = copy_to_user(buf, arr, size);
if(ret)
return -EFAULT;
return size;
}
//在內(nèi)核中屬于用戶空間的地址需要用 __user 修飾
ssize_t cdd_write(struct file *filp, const char __user *buf, size_t size, loff_t *offset)
{
int ret = 0;
printk("enter cdd_write!\n");
if(size>127)
return -ENOMEM;//越界
//拷貝數(shù)據(jù)
ret = copy_from_user(arr, buf, size);
if(ret)
return -EFAULT;
printk("arr = %s\n",arr);
return size;
}
long cdd_ioctl(struct file *filp, unsigned int cmd, unsigned long val)
{
int ret = 0;
printk("enter cdd_ioctl!\n");
//不同的命令對應(yīng)不同的操作
switch(cmd){
case HELLO_DEMO:
printk("HELLO_DEMO!\n");
break;
case HELLO_READ:
{
ret = copy_to_user((int __user *)val, \
&data, sizeof(int));
}
printk("HELLO_READ!\n");
break;
case HELLO_WRITE:
{
ret = copy_from_user(&data, \
(int __user *)val, sizeof(int));
printk("HELLO_WRITE data = %d\n",data);
}
break;
default:
return -EINVAL;
}
return 0;
}
int cdd_release(struct inode *inode, struct file *filp)
{
printk("enter cdd_release!\n");
return 0;
}
//聲明操作函數(shù)集合
struct file_operations cdd_fops = {
.owner = THIS_MODULE,
.open = cdd_open,
.read = cdd_read,
.write = cdd_write,
.unlocked_ioctl = cdd_ioctl,//ioctl接口
.release = cdd_release,//對應(yīng)用戶close接口
};
//加載函數(shù)
int cdd_init(void)
{
int ret;
// 1.注冊字符設(shè)備驅(qū)動(dòng)
ret = register_chrdev(0, "cdd_demo", &cdd_fops);
if(ret<0){
printk("register_chrdev failed!\n");
goto failure_register_chrdev;
}
//構(gòu)建設(shè)備號(hào)
dev = MKDEV(ret,CDD_MINOR);
printk("register_chrdev success!\n");
// 2.注冊設(shè)備類
/*成功會(huì)在/sys/class目錄下出現(xiàn)cdd_class子目錄*/
cdd_class = class_create(THIS_MODULE, "cdd_class");
if(IS_ERR(cdd_class)){
printk("class_create failed!\n");
ret = PTR_ERR(cdd_class);
goto failure_class_create;
}
// 3.創(chuàng)建設(shè)備文件
cdd_device = device_create(cdd_class, NULL, dev,NULL, "cdd");
if(IS_ERR(cdd_device)){
printk("device_create failed!\n");
ret = PTR_ERR(cdd_device);
goto failure_device_create;
}
return 0;
failure_device_create:
class_destroy(cdd_class);
failure_class_create:
unregister_chrdev(MAJOR(dev), "cdd_demo");
failure_register_chrdev:
return ret;
}
//卸載函數(shù)
void cdd_exit(void)
{
//銷毀設(shè)備文件
device_destroy(cdd_class, dev);
//注銷設(shè)備類
class_destroy(cdd_class);
//注銷字符設(shè)備驅(qū)動(dòng)
unregister_chrdev(MAJOR(dev), "cdd_demo");
}
//聲明為模塊的入口和出口
module_init(cdd_init);
module_exit(cdd_exit);
MODULE_LICENSE("GPL");//GPL模塊許可證
MODULE_AUTHOR("xin");//作者
MODULE_VERSION("3.0");//版本
MODULE_DESCRIPTION("charactor driver!");//描述信息
測試模塊
#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <sys/ioctl.h>
#include <sys/types.h>
#include <unistd.h>
#include "iotest.h"
int main()
{
char ch = 0;
char w_buf[10] = "welcome";
char r_buf[10] = {0};
int data = 5;
int fd = open("/dev/cdd",O_RDWR);
if(fd==-1){
perror("open");
exit(-1);
}
printf("open successed!fd = %d\n",fd);
while(1){
ch = getchar();
getchar();
if(ch=='q')
break;
switch(ch){
case 'r':
read(fd,r_buf,sizeof(r_buf));
printf("r_buf = %s\n",r_buf);
break;
case 'w':
write(fd,w_buf,sizeof(r_buf));
break;
case 'd':
ioctl(fd,HELLO_DEMO);
break;
case 'i':
{
ioctl(fd,HELLO_READ,&data);
printf("ioread data=%d\n",data);
}
break;
case 'o':
ioctl(fd,HELLO_WRITE,&data);
break;
default:
printf("error input!\n");
break;
}
sleep(1);
}
close(fd);
return 0;
}
在驅(qū)動(dòng)模塊中使用兩種不同的方式進(jìn)行用戶空間和內(nèi)核空間的數(shù)據(jù)交互,一種是在read()和write()中交換字符串?dāng)?shù)據(jù),一種是在ioctl命令中交換int型數(shù)據(jù)。需要注意在內(nèi)核中屬于用戶空間的地址需要用 __user 修飾。
應(yīng)用測試模塊cdd_test?
?
這大概可以算字符設(shè)備的基本框架吧,里面有些東西沒有細(xì)說,如果感興趣可自行百度。文章來源:http://www.zghlxwxcb.cn/news/detail-662722.html
好了,如果對以上內(nèi)容有什么疑問或建議歡迎在評論區(qū)里提出來^-^。文章來源地址http://www.zghlxwxcb.cn/news/detail-662722.html
到了這里,關(guān)于Linux字符設(shè)備驅(qū)動(dòng)(設(shè)備文件,用戶空間與內(nèi)核空間進(jìn)行數(shù)據(jù)交互,ioctl接口)的文章就介紹完了。如果您還想了解更多內(nèi)容,請?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!