2021-05-12 14:32:11
Linux字元裝置-簡單字元裝置模型
Linux字元裝置
一. 使用字元裝置驅動
1. 編譯/安裝驅動
在Linux系統中,驅動程式通常採用核心模組的程式結構來進行編碼。因此,編譯/安裝一個驅動程式,其實質就是編譯/安裝一個核心模組。
2. 字元裝置檔案
通過字元裝置檔案,應用程式可以使用相應的字元裝置驅動程式來控制字元裝置。
建立字元裝置檔案的方法一般有兩種:
1.使用mknod命令
mknod /dev/檔名 c 主裝置號 次裝置號
2. 使用函數在驅動程式中建立
二. 字元驅動程式設計模型
- 裝置描述結構
- 驅動模型
在Linux系統中,裝置的型別非常繁多,如:字元裝置,塊裝置,網路介面裝置,USB裝置,PCI裝置,平台裝置,混雜裝置……,而裝置型別不同,也意味著其對應的驅動程式模型不同,這樣就導致了我們需要去掌握眾多的驅動程式模型。那麼能不能從這些眾多的驅動模型中提煉出一些具有共性的規則,則是我們能不能學好Linux驅動的關鍵。
1. 裝置描述結構
在任何一種驅動模型中,裝置都會用核心中的一種結構來描述。我們的字元裝置在核心中使用structcdev來描述。
struct cdev {
struct kobject kobj;
struct module *owner;
const struct file_operations *ops; //裝置操作集
struct list_head list;
dev_t dev; //裝置號
unsigned int count; //裝置數
};
1.1 裝置號
檢視/dev目錄下裝置號
1.1 次裝置號
2.1 裝置號-操作
Linux核心中使用dev_t型別來定義裝置號,dev_t這種型別其實質為32位元的unsigned int,其中高12位元為主裝置號,低20位為次裝置號.
問1:如果知道主裝置號,次裝置號,怎麼組合成dev_t型別
答:dev_t dev = MKDEV(主裝置號,次裝置號)
問2: 如何從dev_t中分解出主裝置號?
答: 主裝置號 = MAJOR(dev_t dev)
問3: 如何從dev_t中分解出次裝置號?
答: 次裝置號=MINOR(dev_t dev)
1.1 裝置號-分配
如何為裝置分配一個主裝置號?
靜態申請
開發者自己選擇一個數位作為主裝置號,然後通過函數register_chrdev_region向核心申請使用。缺點:如果申請使用的裝置號已經被核心中的其他驅動使用了,則申請失敗。
動態分配
使用alloc_chrdev_region由核心分配一個可用的主裝置號。
優點:因為核心知道哪些號已經被使用了,所以不會導致分配到已經被使用的號。
1.1 裝置號-登出
不論使用何種方法分配裝置號,都應該在驅動退出時,使用unregister_chrdev_region
函數釋放這些裝置號。
1.2 操作函數集
2.2 操作函數集
Struct file_operations是一個函數指標的集合,定義能在
裝置上進行的操作。結構中的函數指標指向驅動中的函數,
這些函數實現一個針對裝置的操作, 對於不支援的操作則設
置函數指標為 NULL。例如:
struct file_operations dev_fops = {
.llseek = NULL,
.read = dev_read,
.write = dev_write,
.ioctl = dev_ioctl,
.open = dev_open,
.release = dev_release,
};
2.1 字元裝置初始化
2.1 描述結構-分配
cdev變數的定義可以採用靜態和動態兩種辦法
• 靜態分配
struct cdev mdev;
• 動態分配
struct cdev *pdev = cdev_alloc();
2.1 描述結構-初始化
struct cdev的初始化使用cdev_init函數來完成。
cdev_init(struct cdev *cdev, const struct file_operations *fops)
引數:
cdev: 待初始化的cdev結構
fops: 裝置對應的操作函數集
2.1 描述結構-註冊
字元裝置的註冊使用cdev_add函數來完成。
cdev_add(struct cdev *p, dev_t dev, unsigned count)
引數:
p: 待新增到核心的字元裝置結構
dev: 裝置號
count: 該類裝置的裝置個數
2.1 硬體初始化
根據相應硬體的晶片手冊,完成初始化。
2.2 實現裝置操作
2.2 手把手帶你來分析
分析
file_operations
2.2 裝置操作原型
int (*open) (struct inode *, struct file *)
開啟裝置,響應open系統
int (*release) (struct inode *, struct file *);
關閉裝置,響應close系統呼叫
loff_t (*llseek) (struct file *, loff_t, int)
重定位讀寫指標,響應lseek系統呼叫
ssize_t (*read) (struct file *, char __user *, size_t, loff_t *)
從裝置讀取資料,響應read系統呼叫
ssize_t (*write) (struct file *, const char __user *, size_t, loff_t *)
向裝置寫入資料,響應write系統呼叫
2.2 Struct file
在Linux系統中,每一個開啟的檔案,在核心中都會關聯一個struct file,它由核心在開啟檔案時建立, 在檔案關閉後釋放。
重要成員:
loff_t f_pos /*檔案讀寫指標*/
struct file_operations *f_op /*該檔案所對應的操作*/
2.2 Struct inode
每一個存在於檔案系統裡面的檔案都會關聯一個inode 結構,該結構主要用來記錄檔案物理上的資訊。因此, 它和代表開啟檔案的file結構是不同的。一個檔案沒有被開啟時不會關聯file結構,但是卻會關聯一個inode 結構。
重要成員:
dev_t i_rdev:裝置號
3.2 裝置操作-open
open裝置方法是驅動程式用來為以後的操作完成初始化準備工作的。在大部分驅動程式
中,open完成如下工作:
標明次裝置號
啟動裝置
3.2 裝置操作-release
release方法的作用正好與open相反。這個裝置方法有時也稱為close,它應該:
關閉裝置。
3.2 裝置操作-read
read裝置方法通常完成2件事情:
從裝置中讀取資料(屬於硬體存取類操作)
將讀取到的資料返回給應用程式
ssize_t (*read) (struct file *filp, char __user *buff, size_t count, loff_t *offp)
引數分析:
filp:與字元裝置檔案關聯的file結構指標, 由核心建立。
buff : 從裝置讀取到的資料,需要儲存到的位置。由read系統呼叫提供該引數。
count: 請求傳輸的資料量,由read系統呼叫提供該引數。
offp: 檔案的讀寫位置,由核心從file結構中取出後,傳遞進來。
buff引數是來源於使用者空間的指標,這類指標都不能被核心程式碼直接參照,必須使用專門的函數
int copy_from_user(void *to, const void __user *from, int n)
int copy_to_user(void __user *to, const void *from, int n)
3.2 裝置操作-write
write裝置方法通常完成2件事情:
從應用程式提供的地址中取出資料將資料寫入裝置(屬於硬體存取類操作)
ssize_t (*write) (struct file *, const char __user *, size_t, loff_t *)
其引數類似於read
2.3 驅動登出
當我們從核心中解除安裝驅動程式的時候,需要使用cdev_del函數來完成字元裝置的登出。
先寫一個自動分配字元裝置號手動分配字元裝置節點的例子及APP
手動安裝步驟:
Insmod char_dev.ko
檢視字元裝置號
cat /proc/devices
再安裝裝置節點
mknod /dev/my_chardev c 248 0
然後是測試app
./my_char_dev_app 1
核心驅動程式碼char_dev.c
#include <linux/module.h>
#include <linux/init.h>
#include <linux/io.h>
#include <linux/fs.h>
#include <asm/device.h> //下面這三個標頭檔案是由於動態建立需要加的
#include <linux/device.h>
#include <linux/cdev.h>
#include "my_cdev.h"
struct cdev cdev;
dev_t devno;//這裡是動態分配裝置號
int my_cdev_open(struct inode *node,struct file *filp)
{
printk("my_cdev_open sucess!n");
return 0;
}
long my_cdev_ioctl(struct file *filp ,unsigned int cmd ,unsigned long arg)
{
switch(cmd)
{
case LED_ON:
printk("LED_ON is set!n");
return 0;
case LED_OFF:
printk("LED_OFF is set!n");
return 0;
default :
return -EINVAL;
}
}
struct file_operations my_cdev_fops=
{
.open = my_cdev_open,
.unlocked_ioctl = my_cdev_ioctl,
};
static int my_cdev_init(void)
{
int ret;
/**動態分配裝置號*/
ret = alloc_chrdev_region(&devno,0,1,"my_chardev");
if(ret)
{
printk("alloc_chrdev_region fail!n");
unregister_chrdev_region(devno,1);
return ret;
}
else
{
printk("alloc_chrdev_region sucess!n");
}
/**描述結構初始化*/
cdev_init(&cdev,&my_cdev_fops);
/**描述結構註冊*/
ret = cdev_add(&cdev,devno,1);
if(ret)
{
printk("cdev add fail.n");
unregister_chrdev_region(devno,1);
return ret;
}
else
{
printk("cdev add sucess!n");
}
return 0;
}
static void my_cdev_exit(void)
{
cdev_del(&cdev);
unregister_chrdev_region(devno,1);
printk("my_cdev_exit sucess!n");
}
module_init(my_cdev_init);
module_exit(my_cdev_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("YEFEI");
MODULE_DESCRIPTION("YEFEI Driver");
APP標頭檔案my_cdev.h
#ifndef __MY_CDEV_H__
#define __MY_CDEV_H__
#define LED_MAGIC 'L'
#define LED_ON _IO(LED_MAGIC,0)
#define LED_OFF _IO(LED_MAGIC,1)
#endif
APP測試檔案my_char_dev_app.c
#include <sys/stat.h>
#include <sys/types.h>
#include <sys/ioctl.h>
#include <fcntl.h>
#include <stdio.h>
#include "my_cdev.h"
int main(int argc,char *argv[])
{
int fd;
int cmd;
if(argc < 2)
{
printf("Please enter secend param!n");
return 0;
}
cmd = atoi(argv[1]);
fd = open("/dev/my_chardev",O_RDWR);
if(fd < 0)
{
printf("Open dev/my_chardev fail!n");
close(fd);
return 0;
}
switch(cmd)
{
case 1:
ioctl(fd,LED_ON);
break;
case 2:
ioctl(fd,LED_OFF);
break;
default:
break;
}
close(fd);
return 0;
}
2016-02-17
21:54:48
本文永久更新連結地址:http://www.linuxidc.com/Linux/2016-02/128601.htm
相關文章