Linux 字符设备驱动及一些简单的Linux知识
一、linux系統(tǒng)將設(shè)備分為3類:字符設(shè)備、塊設(shè)備、網(wǎng)絡(luò)設(shè)備
1、字符設(shè)備:是指只能一個(gè)字節(jié)一個(gè)字節(jié)讀寫的設(shè)備,不能隨機(jī)讀取設(shè)備內(nèi)存中的某一數(shù)據(jù),讀取數(shù)據(jù)需要按照先后數(shù)據(jù)。字符設(shè)備是面向流的設(shè)備,常見的字符設(shè)備有鼠標(biāo)、鍵盤、串口、控制臺和LED設(shè)備等。
2、塊設(shè)備:是指可以從設(shè)備的任意位置讀取一定長度數(shù)據(jù)的設(shè)備。塊設(shè)備包括硬盤、磁盤、U盤和SD卡等。
每一個(gè)字符設(shè)備或塊設(shè)備都在/dev目錄下對應(yīng)一個(gè)設(shè)備文件。linux用戶程序通過設(shè)備文件(或稱設(shè)備節(jié)點(diǎn))來使用驅(qū)動程序操作字符設(shè)備和塊設(shè)備。
本文主要是介紹字符設(shè)備驅(qū)動程序,從驅(qū)動程序開始,涉及文件操作,一共四個(gè)函數(shù):包括文件的打開,讀,寫,刪除。還有文件的注冊和注銷。
廢話不多說,上源代碼,以代碼為例開始解釋:
1.mydriver.c:
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/fs.h>
#include <linux/init.h>
#include <linux/uaccess.h>
#if CONFIG_MODVERSIONS == 1
#define MODVERSIONS
#include <linux/version.h>
#endif
#define DEVICE_NUM 0 //隨機(jī)產(chǎn)生一個(gè)設(shè)備號
static int device_num = 0; //用來保存創(chuàng)建成功后的設(shè)備號
static char buffer[1024] = "mydriver"; //數(shù)據(jù)緩沖區(qū)
static int open_nr = 0; //打開設(shè)備的進(jìn)程數(shù),用于內(nèi)核的互斥
//函數(shù)聲明
// inode;linux下文件的管理號。
//file:linux一切皆文件。文件結(jié)構(gòu)體代表一個(gè)打開的文件,系統(tǒng)中的每個(gè)打開的文件在內(nèi)核空間都有一個(gè)關(guān)聯(lián)的 struct file。它由內(nèi)核在打開文件時(shí)創(chuàng)建,并傳遞給在文件上進(jìn)行操作的任何函數(shù)。在文件的所有實(shí)例都關(guān)閉后,內(nèi)核釋放這個(gè)數(shù)據(jù)結(jié)構(gòu)
static int mydriver_open(struct inode *inode, struct file *filp);
static int mydriver_release(struct inode *inode, struct file* filp);
static ssize_t mydriver_read(struct file *file, char __user *buf, size_t count, loff_t *f_pos); //loff-t:long long 型
static ssize_t mydriver_write(struct file *file, const char __user *buf, size_t count, loff_t *f_pos); //__user表明參數(shù)是一個(gè)用戶空間的指針,不能在kernel代碼中直接訪問。
//size_t:一個(gè)基本的無符號整數(shù)的C / C + +類型
//填充file_operations結(jié)構(gòu)相關(guān)入口
static struct file_operations mydriver_fops = {
.read = mydriver_read,
.write = mydriver_write,
.open = mydriver_open,
.release = mydriver_release,
};
//打開函數(shù)
static int mydriver_open(struct inode *inode, struct file *filp)
{
printk("\nMain device is %d, and the slave device is %d\n", MAJOR(inode->i_rdev), MINOR(inode->i_rdev)); //把主從設(shè)備號傳入
if (open_nr == 0) {
open_nr++;
try_module_get(THIS_MODULE); //嘗試打開模塊
return 0;
}
else {
printk(KERN_ALERT "Another process open the char device.\n");//進(jìn)程掛起
return -1;
}
}
//讀函數(shù)
static ssize_t mydriver_read(struct file *file, char __user *buf, size_t count, loff_t *f_pos)
{
//if (buf == NULL) return 0;
if (copy_to_user(buf, buffer, sizeof(buffer))) //讀緩沖 ,第一個(gè)參數(shù)是to:用戶空間的地址,第二個(gè)參數(shù)是from,內(nèi)核空間的地址,第三個(gè)參數(shù)是要從內(nèi)核空間拷貝的字節(jié)數(shù)
{
return -1;
}
return sizeof(buffer);
}
//寫函數(shù),將用戶的輸入字符串寫入
static ssize_t mydriver_write(struct file *file, const char __user *buf, size_t count, loff_t *f_pos)
{
//if (buf == NULL) return 0;
if (copy_from_user(buffer, buf, sizeof(buffer))) //寫緩沖
{
return -1;
}
return sizeof(buffer);
}
//釋放設(shè)備函數(shù)
static int mydriver_release(struct inode *inode, struct file* filp)
{
open_nr--; //進(jìn)程數(shù)減1
printk("The device is released!\n");
module_put(THIS_MODULE); //釋放模塊
return 0;
}
//注冊設(shè)備函數(shù)
static int __init mydriver_init(void)
{
int result;
printk(KERN_ALERT "Begin to init Char Device!"); //注冊設(shè)備
//向系統(tǒng)的字符登記表登記一個(gè)字符設(shè)備
result = register_chrdev(DEVICE_NUM, "mydriver", &mydriver_fops); //第一個(gè)參數(shù)等于0,則表示采用系統(tǒng)動態(tài)分配的主設(shè)備號;不為0,則表示靜態(tài)注冊。 第二個(gè)參數(shù)命名, 第三個(gè)參數(shù)為其地址
if (result < 0) {
printk(KERN_WARNING "mydriver: register failure\n");
return -1;
}
else {
printk("mydriver: register success!\n");
device_num = result;
return 0;
}
}
//注銷設(shè)備函數(shù)
static void __exit mydriver_exit(void)
{
printk(KERN_ALERT "Unloading...\n");
unregister_chrdev(device_num, "mydriver"); //注銷設(shè)備
printk("unregister success!\n");
}
//模塊宏定義
module_init(mydriver_init); //模塊加載函數(shù)
module_exit(mydriver_exit); //設(shè)備卸載函數(shù)
MODULE_LICENSE("GPL"); // "GPL" 是指明了 這是GNU General Public License的任意版本
因?yàn)樽⑨寖?nèi)容有限,在后面貼上一些對源代碼的注釋:
inode;linux下文件的管理號。(靜態(tài)的)
file:linux一切皆文件。文件結(jié)構(gòu)體代表一個(gè)打開的文件,系統(tǒng)中的每個(gè)打開的文件在內(nèi)核空間都有一個(gè)關(guān)聯(lián)的 struct file。它由內(nèi)核在打開文件時(shí)創(chuàng)建,并傳遞給在文件上進(jìn)行操作的任何函數(shù)。在文件的所有實(shí)例都關(guān)閉后,內(nèi)核釋放這個(gè)數(shù)據(jù)結(jié)構(gòu)。(動態(tài)的)
__user:The use of char __user *buf is typically found in the linux kernel...denoting that this address is in the user space. For example when writing to disk the kernel copies the contents of *buf into a kernel space buffer before writing it out to disk. Similarly when a process requests a read operation...the device driver at the behest of the kernel reads the desired disk blocks into a kernel space buffer...and then copies them into the user space buffer pointed to by *buf.
__user表明參數(shù)是一個(gè)用戶空間的指針,不能在kernel代碼中直接訪問。也方便其它工具對代碼進(jìn)行檢查。
file-operations:
用來存儲驅(qū)動內(nèi)核模塊提供的 設(shè)備進(jìn)行各種操作的函數(shù)的指針。該結(jié)構(gòu)體的每個(gè)域都對應(yīng)著驅(qū)動內(nèi)核模塊用來處理某個(gè)被請求的事務(wù)的函數(shù)的地址。
MAJOR(inode->i_rdev), MINOR(inode->i_rdev):
如果inode代表一個(gè)設(shè)備,則i_rdev的值為設(shè)備號。為了代碼更好地可移植性,獲取inode的major和minor號應(yīng)該使用imajor和iminor函數(shù)
#include <linux/module.h> :
寫內(nèi)核驅(qū)動的時(shí)候 必須加載這個(gè)頭文件,作用是動態(tài)的將模塊加載到內(nèi)核中去
#include <linux/version.h> :
當(dāng)設(shè)備驅(qū)動需要同時(shí)支持不同版本內(nèi)核時(shí),在編譯階段,內(nèi)核模塊需要知道當(dāng)前使用的內(nèi)核源碼的版本,從而使用相應(yīng)的內(nèi)核 API
try_module_get(&module), module_put(&module):
靈活的模塊計(jì)數(shù)管理接口
#include <linux/kernel.h> :
kernel.h中包含了內(nèi)核打印函數(shù) printk函數(shù) 等
#include <linux/uaccess.h>:
包含了copy_to_user、copy_from_user等內(nèi)核訪問用戶進(jìn)程內(nèi)存地址的函數(shù)定義。(copy_to_user()完成用戶空間到內(nèi)核空間的復(fù)制,函數(shù)copy_from_user()完成內(nèi)核空間到用戶空間的復(fù)制)
copy_to_user(buf, buffer, sizeof(buffer)//
#include <linux/fs.h> :
包含了文件操作相關(guān)struct的定義,例如大名鼎鼎的struct file_operations
#include <linux/init.h> :
內(nèi)核模塊的初始化和注銷函數(shù)就在這個(gè)文件中
2. makefile:
# if KERNELRELEASE is defined, we've been invoked from the
# kernel build system and can use its language.
ifeq ($(KERNELRELEASE),)
????# Assume the source tree is where the running kernel was built
????# You should set KERNELDIR in the environment if it's elsewhere
KERNELDIR ?= ?/lib/modules/$(shell uname -r)/build
????# The current directory is passed to sub-makes as argument
PWD := $(shell pwd)
modules:
$(MAKE) -C $(KERNELDIR) M=$(PWD) modules
modules_install:
$(MAKE) -C $(KERNELDIR) M=$(PWD) modules_install
clean:
rm -rf *.o *~ core .depend .*.cmd *.ko *.mod.c .tmp_versions
.PHONY: modules modules_install clean
else
????# called from kernel build system: just declare what our modules are
obj-m := mydriver.o
endif
3.
#include <sys/types.h>
#include <sys/stat.h>
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#include <fcntl.h>
#include <unistd.h>
#define MAX_SIZE 1024
?
int main(void)
{
int fd;
char buf[MAX_SIZE];
char get[MAX_SIZE];
char devName[20], dir[49] = "/dev/";
system("ls /dev/");
printf("Please input the device's name you wanna to use :");
gets(devName);
strcat(dir, devName);
fd = open(dir, O_RDWR | O_NONBLOCK);
if (fd != -1)
{
read(fd, buf, sizeof(buf));
printf("The device was inited with a string : %s\n", buf);
?// 測試寫
printf("Please input a string ?:\n");
gets(get);
write(fd, get, sizeof(get));
//測試讀
read(fd, buf, sizeof(buf));
system("dmesg");
printf("\nThe string in the device now is : %s\n", buf);
close(fd);
return 0;
}
else
{
printf("Device open failed\n");
return -1;
}
}
以上源代碼均可直接使用,以下是本人對基礎(chǔ)知識的一些拙見,如果您有什么高見請務(wù)必指出,多謝各位大佬!
一.什么是Makefile
(1)KERNELRELEASE在linux內(nèi)核源代碼中的頂層makefile中有定義
(2)shell pwd會取得當(dāng)前工作路徑
(3)shell uname -r會取得當(dāng)前內(nèi)核的版本號
(4)由于make 后面沒有目標(biāo),所以make會在Makefile中的第一個(gè)不是以.開頭的目標(biāo)作為默認(rèn)的目標(biāo)執(zhí)行。
于是modules成為make的目標(biāo)
(5)首先-C表示到存放內(nèi)核的目錄(KERNELDIR)執(zhí)行其makefile,其中保存有內(nèi)核的頂層makefile,M=選項(xiàng)的作用是,
當(dāng)用戶需要以某個(gè)內(nèi)核為基礎(chǔ)編譯一個(gè)外部模塊的話,需要在make modules 命令中加入M=$(PWD),
程序會自動到你所指定的dir目錄中查找模塊源碼,將其編譯,生成KO文件。
(6)obj-m是表示該文件要作為模塊編譯,只是編譯得到globalmem.o而不鏈接進(jìn)內(nèi)核
(7)每個(gè)Makefile中都應(yīng)該寫一個(gè)清空目標(biāo)文件(.o和執(zhí)行文件)的規(guī)則,這不僅便于重編譯
,也很利于保持文件的清潔——clean
(8)make modules_install是把編譯好的模塊拷貝到系統(tǒng)目錄下(一般是/lib/modules/),
拷貝到系統(tǒng)目錄下的目的是方便使用,加載驅(qū)動就使用modprobe globalmem命令,該命令從系統(tǒng)目錄下查找名為globalmem的模塊
二.什么是模塊化編程?為什么要模塊化編程?
(1)內(nèi)核模塊是一些可以讓操作系統(tǒng)內(nèi)核在需要時(shí)載入和執(zhí)行的代碼,同時(shí)在不需要的時(shí)候可以卸載。
這是一個(gè)好的功能,擴(kuò)展了操作系統(tǒng)的內(nèi)核功能,卻不需要重新啟動系統(tǒng),是一種動態(tài)加載的技術(shù)。
(2)內(nèi)核模塊代碼運(yùn)行在內(nèi)核空間,而應(yīng)用程序在用戶空間。應(yīng)用程序的運(yùn)行會形成新的進(jìn)程,而內(nèi)核模塊一般不會
。每當(dāng)應(yīng)用程序執(zhí)行系統(tǒng)調(diào)用時(shí),Linux執(zhí)行模式從用戶空間切換到內(nèi)核空間。
答:Linux 內(nèi)核的整體結(jié)構(gòu)非常龐大,其包含的組件也非常多。我們怎樣把需要的部分都包含在內(nèi)核中呢?
一種方法是把所有需要的功能都編譯到 Linux 內(nèi)核。
這會導(dǎo)致兩個(gè)問題,一是生成的內(nèi)核會很大,二是如果我們要在現(xiàn)有的內(nèi)核中新增或刪除功能,將不得不重新編譯內(nèi)核。
有沒有一種機(jī)制使得編譯出的內(nèi)核本身并不需要包含所有功能,而在這些功能需要被使用的時(shí)候,
其對應(yīng)的代碼可被動態(tài)地加載到內(nèi)核中呢?
Linux 提供了這樣的一種機(jī)制,這種機(jī)制被稱為模塊(Module),可以實(shí)現(xiàn)以上效果。模塊具有以下特點(diǎn)。
1.模塊本身不被編譯入內(nèi)核映像,從而控制了內(nèi)核的大小。
2.模塊一旦被加載,它就和內(nèi)核中的其他部分完全一樣。
三.如何理解設(shè)備號,什么是主設(shè)備號,什么是次設(shè)備號?
一個(gè)字符設(shè)備或塊設(shè)備都有一個(gè)主設(shè)備號和一個(gè)次設(shè)備號。主設(shè)備號用來標(biāo)識與設(shè)備文件相連的驅(qū)動程序,用來反映設(shè)備類型。次設(shè)備號被驅(qū)動程序用來辨別操作的是哪個(gè)設(shè)備,用來區(qū)分同類型的設(shè)備。
linux內(nèi)核中,設(shè)備號用dev_t來描述,2.6.28中定義如下:
typedef u_long dev_t;
在32位機(jī)中是4個(gè)字節(jié),高12位表示主設(shè)備號,低12位表示次設(shè)備號。
四.整個(gè)驅(qū)動從編譯到用測試程序進(jìn)行測試的全過程,需要掌握相關(guān)的各種操作命令
1.在globalmen這個(gè)目錄里面,先make一下,把globalmem.c編譯成模塊globalmem.ko
2.lsmod查看這個(gè)Linux里面有沒有g(shù)lobalmem模塊
3.insmod globalmem.ko 加載globalmem模塊
4.lsmod查看是否加載成功
5.cat /proc/devices查看加載驅(qū)動程序時(shí)生成的設(shè)備 (proc目錄是一個(gè)虛擬文件系統(tǒng),
可以為linux用戶空間和內(nèi)核空間提供交互,它只存在于內(nèi)存中,而不占實(shí)際的flash或硬盤空間)
6.mknod /dev/globalmem c 200 0 給globalmem設(shè)備創(chuàng)建設(shè)備節(jié)點(diǎn),主設(shè)備號為200,次設(shè)備號為0
7.cd /dev 進(jìn)入dev目錄
8.ls 查看dev目錄下是否有g(shù)lobalmem設(shè)備
9.echo '老師你真的很帥!'>/dev/globalmem 向globalmem設(shè)備寫入一句話
10.cat /dev/globalmem 查看globalmem設(shè)備里面的內(nèi)容
11.gcc -o test.c test 用gcc來編譯測試程序
12../test 運(yùn)行測試程序
五.三個(gè)重要的數(shù)據(jù)結(jié)構(gòu)(結(jié)構(gòu)體):file_operations、innode、file,尤其是file_operations
file文件結(jié)構(gòu):
struct file,定義于<linux/fs.h>.文件結(jié)構(gòu)代表一個(gè)打開的文件.(它不特定給設(shè)備驅(qū)動;
系統(tǒng)中每個(gè)打開的文件有一個(gè)關(guān)聯(lián)的 struct file 在內(nèi)核空間).
它由內(nèi)核在 open 時(shí)創(chuàng)建,并傳遞給在文件上操作的任何函數(shù), 直到最后的關(guān)閉.
在文件的所有實(shí)例都關(guān)閉后, 內(nèi)核釋放這個(gè)數(shù)據(jù)結(jié)構(gòu)
.struct file的指針常常稱為filp("file pointer"),源代碼里面沒有這個(gè)結(jié)構(gòu)體,
但是在函數(shù)里面會經(jīng)常用到它,例如
int globalmem_open(struct inode *inode, struct file *filp);
int globalmem_release(struct inode *inode, struct file *filp);
static ssize_t globalmem_read(struct file *filp, char __user *buf, size_t size, loff_t *ppos);
static ssize_t globalmem_write(struct file *filp, const char __user *buf, size_t size, loff_t *ppos);
static loff_t globalmem_llseek(struct file *filp, loff_t offset, int orig);
static long globalmem_ioctl(struct file *filp, unsigned int cmd, unsigned long arg);
inode結(jié)構(gòu):
inode 結(jié)構(gòu)由內(nèi)核在內(nèi)部用來表示文件. 因此, 它和代表打開文件描述符的文件結(jié)構(gòu)是不同的.
可能有代表單個(gè)文件的多個(gè)打開描述符的許多文件結(jié)構(gòu), 但是它們都指向一個(gè)單個(gè) inode 結(jié)構(gòu).
inode 結(jié)構(gòu)包含大量關(guān)于文件的信息. 作為一個(gè)通用的規(guī)則, 這個(gè)結(jié)構(gòu)只有 2 個(gè)成員對于編寫驅(qū)動代碼有用:
dev_ti_rdev;
對于代表設(shè)備文件的節(jié)點(diǎn),這個(gè)成員包含實(shí)際的設(shè)備編號.
structcdev *i_cdev;
structcdev 是內(nèi)核的內(nèi)部結(jié)構(gòu), 代表字符設(shè)備; 這個(gè)成員包含一個(gè)指針, 指向這個(gè)結(jié)構(gòu), 當(dāng)節(jié)點(diǎn)指的是一個(gè)字符設(shè)備文件時(shí).
file_operations結(jié)構(gòu):
結(jié)構(gòu)體file_operations在頭文件 linux/fs.h中定義,用來存儲驅(qū)動內(nèi)核模塊提供的對設(shè)備進(jìn)行各種操作的函數(shù)的指針。
該結(jié)構(gòu)體的每個(gè)域都對應(yīng)著驅(qū)動內(nèi)核模塊用來處理某個(gè)被請求的 事務(wù)的函數(shù)的地址。
如下為C99語法的使用該結(jié)構(gòu)體的方法,并且沒有顯示聲明的結(jié)構(gòu)體成員都被gcc初始化為NULL
static const struct file_operations globalmem_fops = {
.owner = THIS_MODULE,
.llseek = globalmem_llseek,
.read = globalmem_read,
.write = globalmem_write,
.unlocked_ioctl = globalmem_ioctl,/* 在2.6.x的內(nèi)核版本中,文件操作結(jié)構(gòu)體中,
才會有ioctl的字段,高版本中使用unlocked_ioctl */
.open = globalmem_open,
.release = globalmem_release,
};
?
轉(zhuǎn)載于:https://www.cnblogs.com/yuan233/p/8146261.html
總結(jié)
以上是生活随笔為你收集整理的Linux 字符设备驱动及一些简单的Linux知识的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: WinFlash AwdFlash分析
- 下一篇: 机器学习——DBN深度信念网络详解