docker核心底層技術,基于Linux操作系統的底層驅動技術

 2023-10-31 阅读 29 评论 0

摘要:5.3 基于Linux操作系統的底層驅動技術 這里的底層驅動是指Linux下的底層設備驅動,這些驅動通常都是加載在內核態的,可以提供給上層用戶態的應用程序訪問底層設備的能力。也就是說,上層應用程序通過底層的驅動程序可以實現輸入/輸出的管理等功能。 5.3.1

5.3 基于Linux操作系統的底層驅動技術

這里的底層驅動是指Linux下的底層設備驅動,這些驅動通常都是加載在內核態的,可以提供給上層用戶態的應用程序訪問底層設備的能力。也就是說,上層應用程序通過底層的驅動程序可以實現輸入/輸出的管理等功能。

5.3.1 設備驅動概述

設備管理即輸入/輸出子系統,可分為上下兩部分:一部分是上層的,與設備無關,這部分根據輸入/輸出請求,通過特定的設備驅動程序接口來與設備進行通信。另一部分是下層的,與設備有關,常稱為設備驅動程序,它直接與相應設備打交道,并且向上層提供一組訪問接口。

設備管理的目標是對所有外接設備進行良好的讀、寫、控制等操作。由于用戶希望能用同樣的應用程序和命令來訪問設備和普通文件。docker核心底層技術,為此,Linux中的設備管理應用了設備文件這個概念來統一設備的訪問接口。簡單地說,系統試圖使它對所有各類設備的輸入、輸出看起來就好像對普通文件的輸入、輸出一樣。用戶希望能用同樣的應用程序和命令來訪問設備和普通文件。

由于Linux中將設備當做文件來處理,所以對設備進行操作的系統調用和對文件的操作類似,主要包括open()、read()、write()、ioctl()、close()等。應用程序發出系統調用指令

以后,會從用戶態轉換到內核態,通過內核將open()這樣的系統調用轉換成對物理設備的操作。

Linux下的設備驅動任務包括以下兩個。ubuntu device for boot loader、

(1)自動配置和初始化子程序:這部分程序僅在初始化的時候被調用一次。

(2)服務于I/O請求的子程序:這部分是系統調用的結果。在執行這部分程序的時候,系統仍認為和進行調用的進程屬于同一個進程,只是由用戶態變成了內核態,并具有進行此系統調用的用戶程序運行環境,所以可以在其中調用sleep()等與進程運行環境有關的函數。

5.3.2 設備類型分類

縱覽linux/drivers目錄,大概還有35個以上的子目錄,每個子目錄基本上就代表了一種設備驅動,有atm、block、char、misc、input、net、usb、sound、video等。這里只描述在嵌入式系統里面用得最為廣泛的3種設備。

1.字符設備(char device)

字符設備是Linux最簡單的設備,可以像文件一樣訪問。LINUX教程、初始化字符設備時,它的設備驅動程序向Linux登記,并在字符設備向量表中增加一個device_struct數據結構條目,這個設備的主設備標識符用做這個向量表的索引。一個設備的主設備標識符是固定的。chrdevs向量表中的每一個條目,一個device_struct數據結構,包括兩個元素:一個登記設備驅動程序名稱的指針和一個指向一組文件操作的指針。可以參考的代碼是include/linux/ major.h。

一般來說像鼠標、串口、鍵盤等設備都屬于字符設備。

2.塊設備(block device)

塊設備是文件系統的物質基礎,它也可以像文件一樣被訪問。UNIX/LINUX。Linux用blkdevs向量表維護已經登記的塊設備文件。它像chrdevs向量表一樣,使用設備的主設備號作為索引。它的條目也是device_struct數據結構。與字符設備不同的是,塊設備分為SCSI類和IDE類。向Linux內核登記并向核心提供文件操作。一種塊設備類的設備驅動程序向這種類提供和類相關的接口。vim 上一頁下一頁?可以參考的代碼是fs/devices.c。

每一個塊設備驅動程序必須提供普通的文件操作接口和對于buffer cache的接口。每一個塊設備驅動程序填充blk_dev向量表中的blk_dev_struct數據結構。此向量表的索引是設備的主設備號。其中blk_dev_struct數據結構包括一個請求例程的地址和一個指針,指向一個request數據結構的列表,每一個都表達buffer cache向設備讀/寫一塊數據的一個請求。

可以參考的源代碼是drivers/block/ll_rw_blk.c和include/linux/blkdev.h。底層驅動程序是什么。

當buffer cache從一個已登記的設備讀/寫一塊數據,或者希望讀、寫一塊數據到其他

位置時,就在blk_dev_struct中增加一個request數據結構。每個request數據結構都有一個指向一個或多個buffer_head數據結構的指針,每一個都是讀/寫一塊數據的請求。如果buffer_head數據結構被鎖定(buffer_cache),可能會有一個進程在等待這個緩沖區的阻塞進程完成。每一個request數據結構都是從all_request表中分配的。如果request增加到空的request列表中,就調用驅動程序的request函數處理這個request隊列,否則驅動程序只是簡單地處理request隊列中的每一個請求。

塊設備驅動程序和字符設備驅動程序的主要區別是:在對字符設備發出讀、寫請求時,實際的硬件I/O一般緊接著就發生了,塊設備則不然,它利用一塊系統內存作為緩沖區,當用戶進程對設備請求能滿足用戶的要求時,就返回請求的數據,如果不能就調用請求函數來進行實際的I/O操作。docker底層原理。塊設備是主要針對磁盤等慢速設備的,以免耗費過多的CPU時間來等待。

塊設備主要有硬盤、光盤驅動器等。可以查看文件/proc/devices獲得。

3.網絡設備(net device)

網絡設備在系統中的作用類似于一個已掛載的塊設備。塊設備將自己注冊到blk_dev數據及其他內核結構中,然后通過自己的request函數在發生請求時傳輸和接收數據塊,同樣網絡設備也必須在特定的數據結構中注冊自己,以便與外界交換數據包時被調用。網絡設備在Linux里做專門的處理。deepin docker、Linux的網絡系統主要是基于BSD UNIX的Socket機制。在系統和驅動程序之間定義有專門的數據結構(sk_buff)進行數據的傳遞。系統里支持對發送數據和接收數據的緩存,提供流量控制機制,提供對多協議的支持。

4.雜項設備(misc device)

雜項設備也是在嵌入式系統中用得比較多的一種設備驅動,在第11章里面介紹的sub LCD和弦芯片的驅動等都是采用misc device的驅動方式實現的。在Linux內核的include\linux目錄下有Miscdevice.h文件,要把自己定義的misc device從設備定義在這里。其實是因為這些字符設備不符合預先確定的字符設備范疇,所有這些設備采用主編號10,一起歸于misc device,其實misc_register就是用主標號10調用register_chrdev()的。docker底層技術,

5.3.3 設備驅動中關鍵數據結構

1.file_operations數據結構

內核內部通過file結構識別設備,通過file_operations數據結構提供文件系統的入口點

函數。file_operations定義在<linux/fs.h>中的函數指針表。這個結構的每一個成員的名字都對應著一個系統調用。從某種意義上說,寫驅動程序的任務之一就是完成file_operations

中的函數指針。如果在2.4版本內核下開發的驅動很可能在2.6版本中無法使用,需要進行移植。通常file_operations提供了包括open()、write()、read()、release()、poll()、ioctl()等文件系統的入口函數。底層驅動。

下面簡單描述一下幾個重要的入口函數。

1)open():

static int mydriver_open(struct inode *inode, struct file *filp)

當上層對mydriver執行open操作時調用該函數,其中參數inode為設備特殊文件的inode(索引節點)結構指針,參數file是指向這一設備的文件結構指針。open()的主要任務是確定硬件處在就緒狀態、驗證次設備號的合法性(次設備號可以用MINOR(inode→i - rdev)取得)、控制使用設備的進程數、根據執行情況返回狀態碼(0表示成功,負數表示存在錯誤)等。

2)write():

static ssize_t mydriver_write(struct file *filp, const char *buf, size_t size, loff_t *offp)

當設備特殊文件進行write系統調用時,將調用驅動程序的write()函數,向設備發送數據。如果沒有這個函數,write 系統調用會向調用程序返回一個-EINVAL。如果返回值非負,則表示成功寫入的字節數。底層驅動什么意思、Write函數通常就是把數據從用戶空間復制到內核空間,所以在write函數里經常會看到copy_from_user()函數。

3)read():

static ssize_t mydriver_read(struct file *filp, char *buf, size_t size, loff_t *offp)

當對設備特殊文件進行read系統調用時,將調用驅動程序read()函數,用來從設備中讀取數據。當該函數指針被賦為NULL 值時,將導致read 系統調用出錯并返回-EINVAL(“Invalid argument,非法參數”)。函數返回非負值表示成功讀取的字節數(返回值為“signed size”數據類型,通常就是目標平臺上的固有整數類型)。Read()函數則通常是把數據從內核空間復制到用戶空間,一般都會調用copy_to_user()函數。

4)release():

static int mydriver_release(struct inode *inode, struct file *filp)

當最后一個打開設備的用戶進程執行close()系統調用時,內核將調用驅動程序的release()函數,release()函數的主要任務是清理未結束的輸入/輸出操作、釋放資源、用戶自定義其他標志的復位等。從rom中提取底層驅動?

5)ioctl():

static int mydriver_ioctl(struct inode *inode,struct file *filp,unsigned int cmd,unsigned long arg)

該函數是特殊的控制函數,可以通過它向設備傳遞控制信息或從設備取得狀態信息,unsigned int參數為設備驅動程序需要執行的命令代碼,由用戶自定義。unsigned long參數為相應的命令提供參數,類型可以是整型、指針等。如果設備不提供ioctl入口點,則對任何內核未預先定義的請求,ioctl系統調用將返回錯誤(-ENOTTY,“No such ioctl fordevice,該設備無此ioctl命令”)。如果該設備方法返回一個非負值,那么該值會被返回給調用程序以表示調用成功。

6)poll():

static unsigned int mydriver_poll(struct file *filp, poll_table *wait)

poll方法是poll和select 這兩個系統調用的后端實現,用來查詢設備是否可讀、可寫或是否處于某種特殊狀態。

2.inode(索引節點)

文件系統處理的文件所需要的信息在inode(索引節點)中。LINUX系統,一個filesystem可以粗略地分成inode table與data area兩部分。Inode table上有許多的inode,每個inode分別記錄一個檔案的屬性,以及這個檔案分布在哪些data block上。inode包含文件訪問權限、屬主、組、大小、生成時間、訪問時間、最后修改時間等信息。它是Linux管理文件系統的最基本單位,也是文件系統連接任何子目錄、文件的橋梁。inode結構中的靜態信息取自物理設備上的文件系統,由文件系統指定的函數填寫,它只存在于內存中,可以通過inode緩存訪問。雖然每個文件都有相應的inode節點,但是只有在需要的時候,系統才會在內存中為其建立相應的inode數據結構,建立的inode結構將形成一個鏈表,可以通過遍歷這個鏈表得到所需要的文件節點。LINUX和WINDOWS的區別,

3.file結構

file結構主要用于與文件系統對應的設備驅動程序使用。在Linux里,每一個檔案都有一個file結構和inode結構,inode結構是用來讓Kernel做管理的,而file結構則是平常對檔案讀、寫或開啟,關閉所使用的。當然,從user的觀點來看是看不出什么的。比起inode結構,file結構就顯得小多了,file結構也是用串行來管理的,f_next會指到下一個file結構,而f_pprev則會指到上一個file結構的地址,f_dentry會記錄其inode的dentry地址,f_mode為檔案存取種類,f_pos則是目前檔案的offset,每次讀寫都從offset記錄的位置開始讀寫,f_count是此file結構的reference cout,f_flags則是開啟此檔案的模式,f_reada,f_ramax, f_raend,f_ralen,f_rawin則是控制read ahead的參數,f_owner記錄了要接收SIGIO和SIGURG的行程ID或行程群組ID,private_data則是tty driver所使用的字段。

5.3.4 設備驅動程序模板與實現

Linux下的驅動程序雖然復雜,但是總結下來還是有很多的規律可尋。Linux下的設備驅動開始編程時顯得比較容易,可以輕松地開始驅動編寫,但是要把驅動寫好也的確需要花一定的時間去研究。windows底層串口驅動實現?

1.設備驅動模板

設備驅動模板代碼如例程5-4所示。

例程5?4 Mydriver.c

#include <linux/module.h>

#include <linux/config.h>

#include <linux/types.h>

#include <linux/kernel.h>

#include <linux/init.h>

#include <linux/delay.h>

#include <linux/miscdevice.h>

#include <linux/ioctl.h>

#include <linux/interrupt.h>

#include <linux/spinlock.h>

#include <linux/smp_lock.h>

#include <linux/poll.h>

#include <linux/sched.h>

#include <linux/ioport.h>

#include <linux/slab.h>

#include <asm/hardware.h>

#include <asm/io.h>

#include <asm/arch/irqs.h>

#include <asm/irq.h>

#include <asm/signal.h>

#include <asm/uaccess.h>

/*定義設備的從設備號*/

#define MYDRIVER_MINOR 174

/*定義設備相關數據結構*/

typedef struct _MYDRIVER_DEV

{

spinlock_t dev_lock;

wait_queue_head_t oWait;

int open_count;

}MYDRIVER_DEV, *PMYDRIVER_DEV;

/*定義設備狀態數據結構*/

typedef struct _MYDRIVER_DEV_STATS

{

unsigned long rx_intrs;

unsigned long rx_errors;

unsigned long rx_blocks;

unsigned long rx_dropped;

unsigned long tx_intrs;

unsigned long tx_errors;

unsigned long tx_missed;

unsigned long tx_blocks;

unsigned long tx_dropped;

}MYDRIVER_DEV_STATS, * MYDRIVER_DEV_STATS;

unsigned int IntInit=0;

/*定義設備open接口函數*/

static int mydriver_open(struct inode *inode, struct file * filp)

{

int minor;

DBGPRINT("mydriver_open\n");

minor = MINOR(inode->i_rdev);

if ( minor != MYDRIVER_MINOR ) { return -ENODEV; }

#ifdef MODULE

MOD_INC_USE_COUNT; /*打開使用次數累加*/

#endif

mydriver_dev.open_count++;

if ( mydriver_dev.open_count == 1 )

{

DBGPRINT("mydriver_open: first opne\n");

/*第一次打開設備,在這里可以放些設備初始化代碼*/

}

return 0;

}

/*定義設備close接口函數*/

static int mydriver_release(struct inode *inode, struct file *filp)

{

DBGPRINT("mydriver_release\n");

mydriver_dev.open_count--;

if ( mydriver_dev.open_count == 0 )

{

DBGPRINT("mydriver_release: last close\n");

/*設備徹底關閉,這里可以放一些使設備休眠,或者poweroff的代碼*/

}

#ifdef MODULE

MOD_DEC_USE_COUNT; /*打開次數遞減*/

#endif

return 0;

}

/*定義設備read接口函數*/

static ssize_t mydriver_read(struct file *filp, char *buf, size_t size, loff_t *offp)

{

if(size> 8192) size = 8192;

/* copy_to_user()*/

/*copy kernel space to user space. */

/*把數據從內核復制到用戶空間的代碼,可以根據實際添加*/

return size; /*返回字節數*/

}

/*定義設備write接口函數*/

static ssize_t mydriver_write(struct file *filp, const char *buf, size_t size, loff_t *offp)

{

lock_kernel();

DBGPRINT("mydriver_write\n");

if(size> 8192) size = 8192;

/*copy_from_user()*/

/*copy user space to kernel space. */ /*把數據從用戶空間復制到內核空間*/

unlock_kernel();

return size; /*返回字節數*/

}

/*定義設備ioctl接口函數*/

static int mydriver_ioctl(struct inode *inode, struct file *filp, unsigned int cmd, unsigned long arg)

{

int ret = 0;

DBGPRINT("mydriver_ioctl: cmd: 0x%x\n", cmd);

switch(cmd)

{

case cmd1: /*命令字,注意幻數的使用*/

…..

break;

…..

case cmd3:

…..

break;

default:

DBGPRINT("mydriver_ioctl: bad ioctl cmd\n");

ret = -EINVAL;

break;

}

return ret;

}

/*定義設備select函數接口*/

static unsigned int mydriver_poll(struct file *filp, poll_table *wait)

{

poll_wait(filp,&mydriver_dev.oWait,wait);

if(IntInit)

{

IntInit=0;

return POLLIN|POLLRDNORM; //可以寫

}

else { return POLLOUT; //可以讀 }

}

/*定義設備的file_operations*/

static struct file_operations mydriver_fops =

{

owner: THIS_MODULE,

open: mydriver_open,

release: mydriver_release,

read: mydriver_read,

write: mydriver_write,

ioctl: mydriver_ioctl,

poll: mydriver_poll,

};

/*定義設備結構體*/

static struct miscdevice mydriver_miscdev =

{

MYDRIVER_MINOR,

" mydriver ",

& mydriver_fops

};

/*定義設備init函數*/

int __init mydriver_init(void)

{

int ret;

DBGPRINT("mydriver_init\n");

ret =misc_register(&mydriver_miscdev); //注意這里調用misc_register()來注冊

if ( ret )

{

DBGPRINT("misc_register failed: 0x%x\n", ret);

return ret;

}

memset(&mydriver_dev, 0, sizeof(mydriver_dev));

init_waitqueue_head(&mydriver_dev.oWait);

spin_lock_init(&mydriver_dev->dev_lock);

/*這里可以放一些硬件初始化的函數*/

return 0;

}

/*定義設備exit函數*/

void __exit mydriver_exit(void)

{

DBGPRINT("mydriver_exit\n");

misc_deregister(&mydriver_miscdev); //注銷misc dev

}

module_init(mydriver_init);

module_exit(mydriver_exit);

MODULE_LICENSE("GPL");

從上面的模板代碼可以看出,設備驅動主要給上層提供file_operation和ioctl功能,實現上層對于底層設備的管理和讀、寫操作等。另外不同的設備調用的設備注冊和注銷函數有所不同,大家可以區分一下:misc_register()函數、register_chardev()函數、register_netdev()函數及misc_deregister()函數。也可以去分析一下deregister_chardev()函數和deregister_netdev() 函數的不同之處。

通常的設備驅動參照上面的模板就可以實現基本的框架了,當然還需要注意有關硬件的一些操作,包括初始化、參數設置、中斷服務等。這些代碼可以根據系統的設計放在driver_init里面,或者放在第一次打開的時候。docker引擎?

2.設備驅動程序中的中斷

在設備驅動程序中通用的申請中斷的方法如下:

request_irq(INT_DEVICE, device_intr, SA_INTERRUPT, "device_INT", &my- driver_dev)

n INT_DEVICE:對應的硬件中斷號;

n device_intr:中斷服務回調函數名;

n SA_INTERRUPT:申請中斷的方式,表明這是一個快速中斷;

n device_INT:中斷的名字;

n mydriver_dev:申請中斷的設備。

文本框: Tips

INT_DEVICE可以定義在include/asm-arm/arch目錄下的irqs.h文件里面。另外中斷的名字device_INT在什么地方可以看到呢?不妨可以在嵌入式系統啟動之后,cat /proc/interrupts看看打印出來的中斷信息列表里有沒有定義的device_INT這個名字。

request_irq()函數的實體如下:

int request_irq(unsigned int irq, void (*handler)(int, void *, struct pt_regs *),

unsigned long irq_flags, const char * devname, void *dev_id)

{

unsigned long retval;

struct irqaction *action;

if (irq >= NR_IRQS || !irq_desc[irq].valid || !handler ||

(irq_flags & SA_SHIRQ && !dev_id))

return -EINVAL;

action = (struct irqaction *)kmalloc(sizeof(struct irqaction), GFP_ KERNEL);

if (!action)

return -ENOMEM;

action->handler = handler;

action->flags = irq_flags;

action->mask = 0;

action->name = devname;

action->next = NULL;

action->dev_id = dev_id;

retval = setup_arm_irq(irq, action);

if (retval)

kfree(action);

return retval;

}

從上面request_irq的原形函數,可以看出其內部其實是調用了setup_arm_irq()函數在系統中注冊中斷的。當然,如果在module_init函數或者設備對應的open()函數里面申請了中斷,那么相應的就應該在module_exit函數或者module_release函數里面調用free_irq()函數來注銷中斷服務,方法是:

free_irq(INT_DEVICE, &mydriver_dev);

另外,在處理中斷的時候盡量用一些內核提供的像cli()、sti()這樣的函數。

3.利用ioctl進行設備管理

對于底層設備有的時候需要改變設備的運行狀況,有時候需要改變設備的運行參數等。為完成這些參數的設置,上層只要傳遞少量的命令字或參數給底層設備。對于這樣的應用,底層驅動通常是通過給上層提供ioctl函數來實現的。下面先給出一個簡單的ioctl例子:

#define MYDRIVER_MINOR 174

#define MYDRIVER_IOC_MAGIC 'm' /*定義幻數*/

#define MYDRIVER_IOC_BASE 0

/*定義命令字*/

#define MYDRIVER_IOC_ON

_IO(MYDRIVER_IOC_MAGIC, MYDRIVER_IOC_BASE + 1)

#define MYDRIVER_IOC_OFF

_IO(MYDRIVER_IOC_MAGIC, MYDRIVER_IOC_BASE + 2)

#defineMYDRIVER_IOC_SLEEP_IN

_IO(MYDRIVER_IOC_MAGIC, MYDRIVER_IOC_BASE + 3)

#define MYDRIVER_IOC_SLEEP_OUT

_IO(MYDRIVER_IOC_MAGIC, MYDRIVER_IOC_BASE + 4)

#define MYDRIVER_IOC_RESET

_IO(MYDRIVER_IOC_MAGIC, MYDRIVER_IOC_BASE + 5)

#define MYDRIVER_IOC_CLEAR_SCREEN

_IO(MYDRIVER_IOC_MAGIC, MYDRIVER_IOC_BASE + 6)

#define MYDRIVER_IOC_CONTRAST_INC

_IO(MYDRIVER_IOC_MAGIC, MYDRIVER_IOC_BASE + 7)

#define MYDRIVER_IOC_CONTRAST_DEC

_IO(MYDRIVER_IOC_MAGIC, MYDRIVER_IOC_BASE + 8)

#define MYDRIVER_IOC_INIT

_IO(MYDRIVER_IOC_MAGIC, MYDRIVER_IOC_BASE + 9)

static int mydriver_ioctl(struct inode *inode, struct file *filp, unsigned

intcmd, unsigned long arg)

{

int ret = 0;

DBGPRINT("mydriver_ioctl: cmd: 0x%x\n", cmd);

/*根據命令執行對應的操作*/

switch(cmd)

{

case MYDRIVER_IOC_RESET:

mydriver_reset();

break;

case MYDRIVER_IOC_ON:

mydriver_on();

break;

case MYDRIVER_IOC_OFF:

mydriver_off();

break;

case MYDRIVER_IOC_SLEEP_IN:

mydriver_sleep_in();

break;

case MYDRIVER_IOC_SLEEP_OUT:

mydriver_sleep_out();

break;

case MYDRIVER_IOC_CLEAR_SCREEN:

mydriver_clear_screen();

break;

case MYDRIVER_IOC_CONTRAST_DEC:

mydriver_contrast_dec();

break;

case MYDRIVER_IOC_CONTRAST_INC:

mydriver_contrast_inc();

break;

case MYDRIVER_IOC_INIT:

mydriver_initial();

break;

default:

DBGPRINT("mydriver_ioctl: bad ioctl cmd\n");

ret = -EINVAL;

break;

}

return ret;

}

特殊的系統函數調用ioctl(input output control)。一般情況下每個設備可以有自己的ioctl命令,它可以讀ioctl(從進程向內核發送信息)和寫ioctl(返回信息給進程)(注意一下:在此讀、寫的作用是顛倒的,因此ioctl的讀是發送消息給內核而寫是從內核接收消息)或什么也不做。ioctl使用3個參數調用:合適的設備文件的文件描述符,ioctl號及一個參數,該參數是類型長度,因此可以使用一個模型傳遞一些參數。

ioctl號用主設備號,ioctl類型,命令和參數類型編碼。這個ioctl號通常用一個頭文件中的宏調用(_IO,_IOR,_IOW或_IOWR——取決于類型)創建。如果想在自己的模塊中使用ioctl,最好接受官方的ioctl分配。

4.通過proc fs獲取設備狀態

驅動程序加載(insmod)之后,通過什么樣的手段來觀測設備的運行狀況呢?通常可以在file_operation對應的各個函數里面用printk(內核態常用的打印函數)打印出需要了解的調試信息。如果仔細留意的話可以發現在嵌入式系統的文件系統目錄下通常會有proc目錄,在該目錄下可以通過cat interrupt去了解ARM嵌入式系統中ARM處理器中斷的情況,通過cat devices可以了解device設備的狀況。那么設備的運行狀態是不是也可以通過

proc來了解呢?答案是肯定的。因為proc也是一種Linux下用得比較多的用戶態與內核態數據交換的方式,內核的很多數據都是通過這種方式給用戶的,內核的很多參數也可以通過這種方式來讓用戶方便設置的。除了sysctl出口到/proc下的參數,proc提供的大部分內核參數是只讀的。實際上,很多應用嚴重地依賴于proc,因此它幾乎是必不可少的組件。那么如何來使用proc呢?首先一定要包含procfs的頭文件,如#include <linux/proc _fs.h>,其次利用procfs提供的如下API函數。

1)struct proc_dir_entry *create_proc_entry(const char *name, mode_t mode, struct proc_dir_entry *parent)

該函數用于創建一個正常的proc條目,參數name給出了要建立proc條目的名稱,參數mode給出了建立的該proc條目的訪問權限,參數parent指定建立的proc條目所在的目錄。如果要在/proc下建立proc條目,parent應當為NULL,否則它應當為proc_mkdir。返回struct proc_dir_entry結構的指針。

2)extern void remove_proc_entry(const char *name, struct proc_dir_entry *parent)

該函數用于刪除上面函數創建的proc條目,參數name給出要刪除的proc條目的名稱,參數parent指定建立的proc條目所在的目錄。

3)struct proc_dir_entry *proc_mkdir(const char * name, struct proc_dir _entry *parent)

該函數用于創建一個proc目錄,參數name指定要創建的proc目錄的名稱,參數parent為該proc目錄所在的目錄。

4)extern struct proc_dir_entry *proc_mkdir_mode(const char *name, mode _t mode, struct- proc_dir_entry *parent)

該函數用于以一定的模式創建proc目錄,參數name指定要創建的proc目錄的名稱,參數mode給出了建立的該proc目錄的訪問權限,參數parent為該proc目錄所在的目錄。

5)struct proc_dir_entry *proc_symlink(const char * name,struct proc_ dir_entry * parent, const char * dest)

該函數用于建立一個proc條目的符號鏈接,參數name給出要建立的符號鏈接proc條目的名稱,參數parent指定符號鏈接所在的目錄,參數dest指定鏈接到的proc條目名稱。

6)struct proc_dir_entry *create_proc_read_entry(const char *name,mode_t mode, struct proc_dir_entry *base,read_proc_t *read_proc, void * data)

該函數用于建立一個規則的只讀proc條目,參數name給出要建立proc條目的名稱,參數mode給出了建立該proc條目的訪問權限,參數base指定建立proc條目所在的目錄,參數read_proc給出讀取該proc條目的操作函數,參數data為該proc條目的專用數據,它將保存在該proc條目對應的struct file結構的private_data字段中。

7)struct proc_dir_entry *create_proc_info_entry(const char *name,mode_t mode, struct proc_dir_entry *base, get_info_t *get_info)

該函數用于創建一個info型的proc條目,參數name給出了要建立proc條目的名稱,參數mode給出了建立該proc條目的訪問權限,參數base指定建立proc條目所在的目錄,參數get_info指定該proc條目的get_info操作函數。實際上get_info等同于read_proc,如果proc條目沒有定義read_proc,對該proc條目的read操作將使用get_info取代,因此它在功能上非常類似于函數create_proc_read_entry。

8)struct proc_dir_entry *proc_net_create(const char *name, mode_t mode, get_info_t *get_info)

該函數用于在/proc/net目錄下創建一個proc條目,參數name給出了要建立proc條目的名稱,參數mode給出了建立該proc條目的訪問權限,參數get_info指定該proc條目的get_info操作函數。

9)struct proc_dir_entry *proc_net_fops_create(const char *name, mode_t mode, struct file_operations *fops)

該函數也用于在/proc/net下創建proc條目,但是它也同時指定了對該proc條目的文件操作函數。

10)void proc_net_remove(const char *name)

該函數用于刪除前面兩個函數在/proc/net目錄下創建的proc條目。參數name指定要刪除的proc名稱。

除了上述這些函數,值得一提的是結構struct proc_dir_entry,為了創建可讀可寫的proc條目并指定該proc條目的寫操作函數,必須設置上面那些創建proc條目的函數返回指針指向的struct proc_dir_entry結構的write_proc字段,并指定該proc條目的訪問權限有寫權限。

讀者可以通過cat和echo等文件操作函數來查看和設置這些proc文件。下面就在上面的mydriver.c里面增加一個可讀的proc接口功能,來獲取設備的運行狀態。

注意要先在頭文件引用部分添加#include <linux/proc_fs.h>,

接著在mydriver_init(void)里面添加如下一句語句:

create_proc_read_entry("mydriver", 0, NULL, mydriver_stats, &mydriver_dev);

不要忘記在mydriver_exit(void)里面要添加:

remove_proc_entry("mydriver", NULL);

當然最重要的還是要定讀取proc時的操作函數mydriver_stats,具體定義如下:

static int mydriver_stats(char *buf, char **start, off_t offset, int count, int *eof, void *data)

{

PMYDRIVER_DEV dev = (PMYDRIVER_DEV)data;

int len;

char * p = buf;

p += sprintf(p, "mydriver power count: %d\n", dev->power_ count);

p += sprintf(p, "mydriver RX stats: \n");

p += sprintf(p, " intrs: %ld\n", dev->stats.rx_intrs);

p += sprintf(p, " errors: %ld\n", dev->stats.rx_errors);

p += sprintf(p, " blocks: %ld\n", dev->stats.rx_blocks);

p += sprintf(p, " dropped: %ld\n", dev->stats.rx_dropped);

p += sprintf(p, "mydriver TX stats: \n");

p += sprintf(p, " intrs: %ld\n", dev->stats.tx_intrs);

p += sprintf(p, " errors: %ld\n", dev->stats.tx_errors);

p += sprintf(p, " missed: %ld\n", dev->stats.tx_missed);

p += sprintf(p, " blocks: %ld\n", dev->stats.tx_blocks);

p += sprintf(p, " dropped: %ld\n", dev->stats.tx_dropped);

len = p - buf;

return len;

}

通過上面對Linux下驅動的幾方面介紹,讀者應該可以實現基本的驅動框架,嘗試在驅動中處理中斷。還可以利用ioctl來進行設備的管理及使用procfs來獲取設備的運行狀態等。

5.3.5 設備驅動程序的使用

Linux 2.4的內核下驅動編譯出來的名字通常是*.o的文件,而在Linux 2.6的內核下編譯出來的文件是*.ko的文件。在編譯驅動的時候,注意要在編譯的時候加__DKERNEL__和_DMODULE參數。還要注意在makefile文件里面正確地指定KERNELDIR和INCLUDEDIR。驅動程序有兩種加載方式,內核自動加載和手動加載。通常的做法是在調試過程中采用手動加載的方式,等調試好了之后,就可以編譯到內核里面采用自動加載的方式。驅動相對于內核來說就是內核的模塊。

內核驅動模塊的加載命令用insmod,如insmod mydriver.o。這個命令其實就是調用驅動里面的mydriver_init()函數。用insmod命令將編譯好的模塊調入內存時,就是向系統的字符設備表登記了一個字符設備。如果登記成功,返回設備的主設備號,不成功,返回一個負值。那么內核驅動模塊的卸載就是調用rmmod mydriver,這是調用驅動里面的mydriver_exit()函數,它釋放字符設備test在系統字符設備表中占有的表項。當然,上層如果要訪問內核的驅動模塊,還需要在dev目錄下添加設備訪問節點,在dev目錄下執行mknod c主設備號從設備號:

mknod mydriver c 10 174

從設備號可以從0-254,主設備號在include/linux/major.h文件里可以看到具體的定義,在include/linux/miscdevices.h文件里可以看到從設備號的一些定義,在定義自己的設備號的時候注意不要和系統內核中原有的設備號沖突。

另外一點需要注意如果使用了devfs文件系統的話,設備節點的目錄是不同的。設備的訪問節點要改成/dev/misc/mydriver。可以通過查看編譯出來的system.map文件或cat /proc/ksyms查看底層驅動導出的函數。

當內核加載了驅動之后,上層就可以通過驅動對底層設備進行操作了。如例程5-5所示的代碼是一個簡單的對mydriver進行讀寫和ioctl的例子。

例程5?5 test.c

#include <sys/types.h>

#include <sys/stat.h>

#include <sys/ioctl.h>

#include <fcntl.h>

#include <stdio.h>

#include <unistd.h>

#if 0

#define DEV_MYDRIVER "/dev/misc/mydriver"

#else

#define DEV_MYDRIVER "/dev/mydriver"

#endif

#define MYDRIVER_IOC_RESET

_IO(MYDRIVER_IOC_MAGIC, MYDRIVER_IOC_BASE + 5)

int dev_cmd(const char * dev, int cmd, unsigned long arg)

{

int fd;

fd = open(dev, O_RDWR);

if ( fd < 0 )

{

perror(dev);

return -1;

}

ioctl(fd, cmd, arg);

close(fd);

return 0;

}

void test_ioctl(void)

{

dev_cmd(DEV_MYDRIVER,MYDRIVER_IOC_RESET,0);

}

void test_mydriver(void)

{

int i, j;

int fd;

char buf[34];

i = 0;

while ( 1 )

{

i++;

printf("\n***** count: %d *****\n", i);

fd = open(DEV_MYDRIVER, O_RDWR);

for ( j = 0; j < 50; j++ )

{

read(fd, buf, sizeof(buf));

write(fd, buf, sizeof(buf));

}

close(fd);

}

}

int main(int argc, char * argv[])

{

if ( argc < 2 )

{

printf("test <a|c>\n");

return 1;

}

switch ( argv[1][0] )

{

case 'a':

test_mydriver();

break;

case 'c':

test_ioctl ();

break;

}

return 0;

}


文章轉載:luckybirdtom

版权声明:本站所有资料均为网友推荐收集整理而来,仅供学习和研究交流使用。

原文链接:https://hbdhgg.com/2/165680.html

发表评论:

本站为非赢利网站,部分文章来源或改编自互联网及其他公众平台,主要目的在于分享信息,版权归原作者所有,内容仅供读者参考,如有侵权请联系我们删除!

Copyright © 2022 匯編語言學習筆記 Inc. 保留所有权利。

底部版权信息