歡迎來到Linux教程網
Linux教程網
Linux教程網
Linux教程網
Linux教程網 >> Linux基礎 >> Linux教程 >> Linux字符設備-簡單字符設備模型

Linux字符設備-簡單字符設備模型

日期:2017/2/28 13:54:19   编辑:Linux教程

Linux字符設備

一. 使用字符設備驅動

1. 編譯/安裝驅動
在Linux系統中,驅動程序通常采用內核模塊的程序結構來進行編碼。因此,編譯/安裝一個驅動程序,其實質就是編譯/安裝一個內核模塊。

2. 字符設備文件

通過字符設備文件,應用程序可以使用相應的字符設備驅動程序來控制字符設備。


創建字符設備文件的方法一般有兩種:
1.使用mknod命令
mknod /dev/文件名 c 主設備號 次設備號
2. 使用函數在驅動程序中創建

二. 字符驅動編程模型

  1. 設備描述結構

  1. 驅動模型
    在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

Copyright © Linux教程網 All Rights Reserved