Linux設備驅動開發詳解筆記一

1.1設備驅動的作用

 

驅使硬件設備行動

驅動與底層硬件直接打交道, 按照硬件設備的具體工作方式, 讀寫設備的寄存器, 完成設備的輪詢、 中斷處理、 DMA通信, 進行物理內存向虛擬內存的映射等, 最終讓通信設備能收發數據, 讓顯示設備能顯示文字和畫面, 讓存儲設備能記錄文件和數據。

設備驅動充當了硬件和應用軟件之間的紐帶, 應用軟件時只需要調用系統軟件的應用編程接口(API) 就可讓硬件去完成要求的工作。

1.4.1 設備的分類及特點

 

計算機系統的硬件組成CPU、存儲器、外設

大部分的CPU內部集成了存儲器和外設適配器(UART、I2C控制器、SPI控制器、USB控制器、SDRAM控制器、GPU、視頻解碼器)

3大基礎大類:字符設備、塊設備、網絡設備

字符設備:以串行順序依次進行訪問的設備

塊設備:可以按照任意順序進行訪問,以塊爲單位

網絡設備:面向數據包的接收和發送而設計,使用套接字接口,不傾向於文件系統的接點。

 

1.6.1 無操作系統時的LED驅動

 

在嵌入式系統中,LED一般直接由CPU的GPIO(通用可編程I/O)口控制。

GPIO一般由兩組寄存器控制(控制寄存器和數據寄存器)。

控制寄存器可設置GPIO的工作方式爲輸入或輸出。

 

ToVirtual()的作用:當系統啓動了硬件MMU,根據物理地址和虛擬地址的映射關係,將寄存器的物理地址轉化爲虛擬地址。

 

2.3.2 I2C

I2C(Inter-Integrated Circuit內置集成電路)總線時兩線式串行總線

I2C 總線簡單有效,佔用PCB空間小,芯片引腳數量少,設計成本低。

支持多主控模式,任何能夠進行發送和接收的設備都可以成爲主設備。

主控能夠控制數據的傳輸和時鐘頻率,在任意時刻只能有一個主控

 

兩個信號:數據線SDA和時鐘SCL

I2C設備上的串行數據線SDA接口電路是雙向的, 輸出電路用於向總線上發送數據, 輸入電路用於接收總線上的數據。 同樣地, 串行時鐘線SCL也是雙向的, 作爲控制總線數據傳送的主機要通過SCL輸出電

路發送時鐘信號, 並檢測總線上SCL上的電平以決定什麼時候發下一個時鐘脈衝電平; 作爲接收主機命令的從設備需按總線上SCL的信號發送或接收SDA上的信號, 它也可以向SCL線發出低電平信號以延長總線時鐘信號週期

 

2.3.3 SPI

SPI( Serial Peripheral Interface, 串行外設接口) 總線系統是一種同步串行外設接口, 它可以使CPU與各種外圍設備以串行方式進行通信以交換信息。 一般主控SoC作爲SPI的“主”, 而外設作爲SPI的“從”。

 

SPI接口一般使用4條線: 串行時鐘線( SCLK) 、 主機輸入/從機輸出數據線MISO、 主機輸出/從機輸入數據線MOSI和低電平有效的從機選擇線SS( 在不同的文獻裏, 也常稱爲nCS、 CS、 CSB、 CSN、nSS、 STE、 SYNC等)

 

3.3.2 Linux內核的組成部分

Linux內核主要由進程調度(SCHED) 、 內存管理(MM) 、 虛擬文件系統(VFS) 、網絡接口(NET) 和進程間通信(IPC) 5個子系統組成。

 

1.進程調度

 

進程調度控制系統中的多個進程對CPU的訪問, 使得多個進程能在CPU中“微觀串行, 宏觀並行”地執行。 進程調度處於系統的中心位置, 內核中其他的子系統都依賴它, 因爲每個子系統都需要掛起或恢復進程

 

在Linux內核中, 使用task_struct結構體來描述進程, 該結構體中包含描述該進程內存資源、 文件系統資源、 文件資源、 tty資源、 信號處理等的指針。 Linux的線程採用輕量級進程模型來實現, 在用戶空間通過pthread_create() API創建線程的時候, 本質上內核只是創建了一個新的task_struct, 並將新task_struct的所有資源指針都指向創建它的那個task_struct的資源指針。

 

絕大多數進程(以及進程中的多個線程) 是由用戶空間的應用創建的, 當它們存在底層資源和硬件訪問的需求時, 會通過系統調用進入內核空間。 有時候, 在內核編程中, 如果需要幾個併發執行的任務, 可以啓動內核線程, 這些線程沒有用戶空間。 啓動內核線程的函數爲

pid_t kernel_thread(int (*fn)(void *), void *arg, unsigned long flags);

 

2.內存管理

32位處理器的Linux的每個進程享有4GB的內存空間, 0~3GB屬於用戶空間, 3~4GB屬於內核空間,Kernel Features→Memory split可調整內核空間和用戶空間的界限

 

3.虛擬文件系統

Linux虛擬文件系統隱藏了各種硬件的具體細節, 爲所有設備提供了統一的接口。 而

且, 它獨立於各個具體的文件系統, 是對各種文件系統的一個抽象。 它爲上層的應用程序提供了統一的vfs_read() 、 vfs_write() 等接口, 並調用具體底層文件系統或者設備驅動中實現的file_operations結構體的成員函數。

 

4.網絡接口

 

網絡接口可分爲網絡協議和網絡驅動程序, 網絡協議部分負責實現每一種可能的網絡傳輸協議, 網絡設備驅動程序負責與硬件設備通信, 每一種可能的硬件設備都有相應的設備驅動程序。

 

 

3.3.3 Linux內核空間與用戶空間

內核空間和用戶空間這兩個名詞用來區分程序執行的兩種不同狀態, 它們使用不同的地址空間。Linux只能通過系統調用和硬件中斷完成從用戶空間到內核空間的控制轉移

 

3.4.1 Linux內核的編譯

make config

編譯內核和模塊

make ARCH=arm zImage

make ARCH=arm modules

第4章 Linux內核模塊

4.1 Linux內核模塊簡介

編譯進內核:生成的內核變大,新增刪除功能,重新編譯內核。

模塊機制:模塊本身不被編譯進內核映像,控制內核大小;模塊一旦被加載,和內核其他部分一樣。

加載:insmod ./hello.ko

卸載:rmmod hello

查看模塊:

lsmod 讀取分析/proc/modules

或者cat /proc/modules

modprobe 加載模塊同時加載模塊的依賴模塊

modprobe-r hello 卸載模塊及其依賴模塊

modinfo hello 獲取模塊信息

4.3 模塊加載函數

模塊加載函數以“module_init(函數名) ”的形式被指定。

request_model()代碼中加載內核模塊

所有標識爲__init的函數如果直接編譯進入內核, 成爲內核鏡像的一部分, 在連接的時候

都會放在.init.text這個區段內。

#define _ _init _ _attribute_ _ ((_ _section_ _ (".init.text")))

4.4 模塊卸載函數

模塊卸載函數在模塊卸載的時候執行, 而不返回任何值, 且必須以“module_exit(函數名) ”的形式來

指定。 通常來說, 模塊卸載函數要完成與模塊加載函數相反的功能。

 

第5章 Linux文件系統與設備文件

5.2.2 Linux文件系統與設備驅動

應用程序和VFS之間的接口是系統調用, 而VFS與文件系統以及設備文件之間的接口是file_operations

結構體成員函數, 這個結構體包含對文件進行打開、 關閉、 讀寫、 控制的一系列成員函數

 

 

1.file結構體

file結構體代表一個打開的文件, 系統中每個打開的文件在內核空間都有一個關聯的struct file。 它由內

核在打開文件時創建, 並傳遞給在文件上進行操作的任何函數。 在文件的所有實例都關閉後, 內核釋放這

個數據結構。 在內核和驅動源代碼中, struct file的指針通常被命名爲file或filp(即file pointer) 。

2.inode結構體

VFS inode包含文件訪問權限、 屬主、 組、 大小、 生成時間、 訪問時間、 最後修改時間等信息。

主設備號是與驅動對應的概念, 同一類設備一般使用相同的主設備號, 不同類的設備一般使用不同的

主設備號

因爲同一驅動可支持多個同類設備, 因此用次設備號來描述使用該驅動的設備的序號, 序號一般從0開始。

device_driver和device分別表示驅動和設備, 而這兩者都必須依附於一種總線, 因此都包含struct

bus_type指針。 在Linux內核中, 設備和驅動是分開註冊的, 註冊1個設備的時候, 並不需要驅動已經存

在, 而1個驅動被註冊的時候, 也不需要對應的設備已經被註冊。 設備和驅動各自湧向內核, 而每個設備

和驅動湧入內核的時候, 都會去尋找自己的另一半, 而正是bus_type的match() 成員函數將兩者捆綁在一

起。

 

總線、 驅動和設備最終都會落實爲sysfs中的1個目錄, 因爲進一步追蹤代碼會發現, 它們實際

上都可以認爲是kobject的派生類, kobject可看作是所有總線、 設備和驅動的抽象基類, 1個kobject對應

sysfs中的1個目錄。

 

第6章 字符設備驅動

6.1.1 cdev結構體

1struct cdev {

2 struct kobject kobj; /* 內嵌的kobject對象 */

3 struct module *owner; /* 所屬模塊*/

4 struct file_operations *ops; /* 文件操作結構體*/

5 struct list_head list;

6 dev_t dev; /* 設備號*/

7 unsigned int count;

8}

cdev_init() 函數用於初始化cdev的成員, 並建立cdev和file_operations之間的連接

void cdev_init(struct cdev *cdev, struct file_operations *fops)

cdev_alloc( ) 函數用於動態申請一個cdev內存

6.1.3 file_operations結構體

file_operations結構體中的成員函數是字符設備驅動程序設計的主體內容, 這些函數實際會在應用程序

進行Linux的open() 、 write() 、 read() 、 close() 等系統調用時最終被內核調用。

mmap( ) 函數將設備內存映射到進程的虛擬地址空間中, 如果設備驅動未實現此函數, 用戶進行

mmap( ) 系統調用時將獲得-ENODEV返回值。 這個函數對於幀緩衝等設備特別有意義, 幀緩衝被映射到

用戶空間後, 應用程序可以直接訪問它而無須在內核和應用間進行內存複製。 它與用戶空間應用程序中的

void*mmap( void*addr, size_t length, int prot, int flags, int fd, off_t offset) 函數對應

 

1/* 設備結構體

2struct xxx_dev_t {

3 struct cdev cdev;

4 ...

5} xxx_dev;

6/* 設備驅動模塊加載函數

7static int _ _init xxx_init(void)

8{

9 ...

10 cdev_init(&xxx_dev.cdev, &xxx_fops); /* 初始化cdev */

11 xxx_dev.cdev.owner = THIS_MODULE;

12 /* 獲取字符設備號*/

13 if (xxx_major) {

14 register_chrdev_region(xxx_dev_no, 1, DEV_NAME);

15 } else {

16 alloc_chrdev_region(&xxx_dev_no, 0, 1, DEV_NAME);

17 }

18

19 ret = cdev_add(&xxx_dev.cdev, xxx_dev_no, 1); /* 註冊設備*/

20 ...

21}

22/* 設備驅動模塊卸載函數*/

23static void _ _exit xxx_exit(void)

24{

25 unregister_chrdev_region(xxx_dev_no, 1); /* 釋放佔用的設備號*/

26 cdev_del(&xxx_dev.cdev); /* 註銷設備*/

27 ...

28}

1 /* 讀設備*/

2 ssize_t xxx_read(struct file *filp, char __user *buf, size_t count,

3 loff_t*f_pos)

4 {

5 ...

6 copy_to_user(buf, ..., ...);

7 ...

8 }

 

9 /* 寫設備*/

10 ssize_t xxx_write(struct file *filp, const char __user *buf, size_t count,

11 loff_t *f_pos)

12 {

13 ...

14 copy_from_user(..., buf, ...);

15 ...

16 }

17 /* ioctl函數 */

18 long xxx_ioctl(struct file *filp, unsigned int cmd,

19 unsigned long arg)

20 {

21 ...

22 switch (cmd) {

23 case XXX_CMD1:

24 ...

25 break;

26 case XXX_CMD2:

27 ...

28 break;

29 default:

30 /* 不能支持的命令 */

31 return - ENOTTY;

32 }

33 return 0;

34 }

 

內核空間和用戶空間內存複製copy_from_user()、copy_to_user()

unsigned long copy_from_user(void *to, const void _ _user *from, unsigned long count);

unsigned long copy_to_user(void _ _user *to, const void *from, unsigned long count);

 

如果要複製的內存是簡單類型, 如char、 int、 long等, 則可以使用簡單的put_user() 和

get_user()

 

int val; /* 內核空間整型變量

...

get_user(val, (int *) arg); /* 用戶→內核, arg是用戶空間的地址 */

...

put_user(val, (int *) arg); /* 內核→用戶, arg是用戶空間的地址 */

#include <linux/module.h>
#include <linux/fs.h>
#include <linux/init.h>
#include <linux/cdev.h>
#include <linux/slab.h>
#include <linux/uaccess.h>

#define GLOBALMEM_SIZE	0x1000
#define MEM_CLEAR 0x1
#define GLOBALMEM_MAJOR 230

static int globalmem_major = GLOBALMEM_MAJOR;
module_param(globalmem_major, int, S_IRUGO);

struct globalmem_dev {
	struct cdev cdev;
	unsigned char mem[GLOBALMEM_SIZE];
};

struct globalmem_dev *globalmem_devp;

static int globalmem_open(struct inode *inode, struct file *filp)
{
	filp->private_data = globalmem_devp;
	return 0;
}

static int globalmem_release(struct inode *inode, struct file *filp)
{
	return 0;
}

static long globalmem_ioctl(struct file *filp, unsigned int cmd,
			    unsigned long arg)
{
	struct globalmem_dev *dev = filp->private_data;

	switch (cmd) {
	case MEM_CLEAR:
		memset(dev->mem, 0, GLOBALMEM_SIZE);
		printk(KERN_INFO "globalmem is set to zero\n");
		break;

	default:
		return -EINVAL;
	}

	return 0;
}

static ssize_t globalmem_read(struct file *filp, char __user * buf, size_t size,
			      loff_t * ppos)
{
	unsigned long p = *ppos;
	unsigned int count = size;
	int ret = 0;
	struct globalmem_dev *dev = filp->private_data;

	if (p >= GLOBALMEM_SIZE)
		return 0;
	if (count > GLOBALMEM_SIZE - p)
		count = GLOBALMEM_SIZE - p;

	if (copy_to_user(buf, dev->mem + p, count)) {
		ret = -EFAULT;
	} else {
		*ppos += count;
		ret = count;

		printk(KERN_INFO "read %u bytes(s) from %lu\n", count, p);
	}

	return ret;
}

static ssize_t globalmem_write(struct file *filp, const char __user * buf,
			       size_t size, loff_t * ppos)
{
	unsigned long p = *ppos;
	unsigned int count = size;
	int ret = 0;
	struct globalmem_dev *dev = filp->private_data;

	if (p >= GLOBALMEM_SIZE)
		return 0;
	if (count > GLOBALMEM_SIZE - p)
		count = GLOBALMEM_SIZE - p;

	if (copy_from_user(dev->mem + p, buf, count))
		ret = -EFAULT;
	else {
		*ppos += count;
		ret = count;

		printk(KERN_INFO "written %u bytes(s) from %lu\n", count, p);
	}

	return ret;
}

static loff_t globalmem_llseek(struct file *filp, loff_t offset, int orig)
{
	loff_t ret = 0;
	switch (orig) {
	case 0:
		if (offset < 0) {
			ret = -EINVAL;
			break;
		}
		if ((unsigned int)offset > GLOBALMEM_SIZE) {
			ret = -EINVAL;
			break;
		}
		filp->f_pos = (unsigned int)offset;
		ret = filp->f_pos;
		break;
	case 1:
		if ((filp->f_pos + offset) > GLOBALMEM_SIZE) {
			ret = -EINVAL;
			break;
		}
		if ((filp->f_pos + offset) < 0) {
			ret = -EINVAL;
			break;
		}
		filp->f_pos += offset;
		ret = filp->f_pos;
		break;
	default:
		ret = -EINVAL;
		break;
	}
	return ret;
}

static const struct file_operations globalmem_fops = {
	.owner = THIS_MODULE,
	.llseek = globalmem_llseek,
	.read = globalmem_read,
	.write = globalmem_write,
	.unlocked_ioctl = globalmem_ioctl,
	.open = globalmem_open,
	.release = globalmem_release,
};

static void globalmem_setup_cdev(struct globalmem_dev *dev, int index)
{
	int err, devno = MKDEV(globalmem_major, index);

	cdev_init(&dev->cdev, &globalmem_fops);
	dev->cdev.owner = THIS_MODULE;
	err = cdev_add(&dev->cdev, devno, 1);
	if (err)
		printk(KERN_NOTICE "Error %d adding globalmem%d", err, index);
}

static int __init globalmem_init(void)
{
	int ret;
	dev_t devno = MKDEV(globalmem_major, 0);

	if (globalmem_major)
		ret = register_chrdev_region(devno, 1, "globalmem");
	else {
		ret = alloc_chrdev_region(&devno, 0, 1, "globalmem");
		globalmem_major = MAJOR(devno);
	}
	if (ret < 0)
		return ret;

	globalmem_devp = kzalloc(sizeof(struct globalmem_dev), GFP_KERNEL);
	if (!globalmem_devp) {
		ret = -ENOMEM;
		goto fail_malloc;
	}

	globalmem_setup_cdev(globalmem_devp, 0);
	return 0;

 fail_malloc:
	unregister_chrdev_region(devno, 1);
	return ret;
}
module_init(globalmem_init);

static void __exit globalmem_exit(void)
{
	cdev_del(&globalmem_devp->cdev);
	kfree(globalmem_devp);
	unregister_chrdev_region(MKDEV(globalmem_major, 0), 1);
}
module_exit(globalmem_exit);

MODULE_AUTHOR("Barry Song <[email protected]>");
MODULE_LICENSE("GPL v2");

mknod /dev/globalmem c 230 0
創建“/dev/globalmem”設備節點, 並通過“echo'hello world'>/dev/globalmem”命令和“cat/dev/globalmem”命令分
別驗證設備的寫和讀, 結果證明“hello world”字符串被正確地寫入了globalmem字符設備:
 

第7章 Linux設備驅動中的併發控制
7.1 併發與競態
併發(Concurrency) 指的是多個執行單元同時、 並行被執行, 而併發的執行單元對共享資源(硬件資
源和軟件上的全局變量、 靜態變量等) 的訪問則很容易導致競態(Race Conditions) 。
7.2 編譯亂序和執行亂序
#define barrier() __asm__ __volatile__("": : :"memory")
7.3 中斷屏蔽
在單CPU範圍內避免競態的一種簡單而有效的方法是在進入臨界區之前屏蔽系統的中斷, 但是在驅動
編程中不值得推薦, 驅動通常需要考慮跨平臺特點而不假定自己在單核上運行。 CPU一般都具備屏蔽中斷
和打開中斷的功能, 這項功能可以保證正在執行的內核執行路徑不被中斷處理程序所搶佔, 防止某些競態
條件的發生。
local_irq_disable() /* 屏蔽中斷 */
. . .
critical section /* 臨界區*/
. . .
local_irq_enable() /* 開中斷*/
7.4 原子操作
原子操作可以保證對一個整型數據的修改是排他性的。 Linux內核提供了一系列函數來實現內核中的
原子操作, 這些函數又分爲兩類, 分別針對位和整型變量進行原子操作。 位和整型變量的原子操作都依賴
於底層CPU的原子操作, 因此所有這些函數都與CPU架構密切相關。 對於ARM處理器而言, 底層使用
LDREX和STREX指令, 比如atomic_inc() 底層的實現會調用到atomic_add()
7.5 自旋鎖
/* 定義一個自旋鎖*/
spinlock_t lock;
spin_lock_init(&lock);
spin_lock (&lock) ; /* 獲取自旋鎖, 保護臨界區 */
. . ./* 臨界區*/
spin_unlock (&lock) ; /* 解鎖*/

1) 自旋鎖實際上是忙等鎖, 當鎖不可用時, CPU一直循環執行“測試並設置”該鎖直到可用而取得該
鎖, CPU在等待自旋鎖時不做任何有用的工作, 僅僅是等待。 因此, 只有在佔用鎖的時間極短的情況下,
使用自旋鎖纔是合理的。 當臨界區很大, 或有共享設備的時候, 需要較長時間佔用鎖, 使用自旋鎖會降低
系統的性能。
2) 自旋鎖可能導致系統死鎖。 引發這個問題最常見的情況是遞歸使用一個自旋鎖, 即如果一個已經
擁有某個自旋鎖的CPU想第二次獲得這個自旋鎖, 則該CPU將死鎖。
3) 在自旋鎖鎖定期間不能調用可能引起進程調度的函數。 如果進程獲得自旋鎖之後再阻塞, 如調用
copy_from_user( ) 、 copy_to_user( ) 、 kmalloc( ) 和msleep( ) 等函數, 則可能導致內核的崩潰。
4) 在單核情況下編程的時候, 也應該認爲自己的CPU是多核的, 驅動特別強調跨平臺的概念。
7.6 信號量
7.7 互斥體
struct mutex my_mutex; /* 定義mutex */
mutex_init(&my_mutex); /* 初始化mutex */
mutex_lock(&my_mutex); /* 獲取mutex */
... /* 臨界資源*/
mutex_unlock(&my_mutex); /* 釋放mutex */

自旋鎖和互斥體選用的3項原則。
1) 當鎖不能被獲取到時, 使用互斥體的開銷是進程上下文切換時間, 使用自旋鎖的開銷是等待獲取
自旋鎖(由臨界區執行時間決定) 。 若臨界區比較小, 宜使用自旋鎖, 若臨界區很大, 應使用互斥體。
2) 互斥體所保護的臨界區可包含可能引起阻塞的代碼, 而自旋鎖則絕對要避免用來保護包含這樣代
碼的臨界區。 因爲阻塞意味着要進行進程的切換, 如果進程被切換出去後, 另一個進程企圖獲取本自旋
鎖, 死鎖就會發生。
3) 互斥體存在於進程上下文, 因此, 如果被保護的共享資源需要在中斷或軟中斷情況下使用, 則在
互斥體和自旋鎖之間只能選擇自旋鎖。 當然, 如果一定要使用互斥體, 則只能通過mutex_trylock() 方式
進行, 不能獲取就立即返回以避免阻塞

#include <linux/module.h>
#include <linux/fs.h>
#include <linux/init.h>
#include <linux/cdev.h>
#include <linux/slab.h>
#include <linux/uaccess.h>

#define GLOBALMEM_SIZE	0x1000
#define MEM_CLEAR 0x1
#define GLOBALMEM_MAJOR 230

static int globalmem_major = GLOBALMEM_MAJOR;
module_param(globalmem_major, int, S_IRUGO);

struct globalmem_dev {
	struct cdev cdev;
	unsigned char mem[GLOBALMEM_SIZE];
	struct mutex mutex;
};

struct globalmem_dev *globalmem_devp;

static int globalmem_open(struct inode *inode, struct file *filp)
{
	filp->private_data = globalmem_devp;
	return 0;
}

int globalmem_release(struct inode *inode, struct file *filp)
{
	return 0;
}

static long globalmem_ioctl(struct file *filp, unsigned int cmd,
			    unsigned long arg)
{
	struct globalmem_dev *dev = filp->private_data;

	switch (cmd) {
	case MEM_CLEAR:
		mutex_lock(&dev->mutex);
		memset(dev->mem, 0, GLOBALMEM_SIZE);
		mutex_unlock(&dev->mutex);

		printk(KERN_INFO "globalmem is set to zero\n");
		break;

	default:
		return -EINVAL;
	}

	return 0;
}

static ssize_t globalmem_read(struct file *filp, char __user * buf, size_t size,
			      loff_t * ppos)
{
	unsigned long p = *ppos;
	unsigned int count = size;
	int ret = 0;
	struct globalmem_dev *dev = filp->private_data;

	if (p >= GLOBALMEM_SIZE)
		return 0;
	if (count > GLOBALMEM_SIZE - p)
		count = GLOBALMEM_SIZE - p;

	mutex_lock(&dev->mutex);
	if (copy_to_user(buf, dev->mem + p, count)) {
		ret = -EFAULT;
	} else {
		*ppos += count;
		ret = count;

		printk(KERN_INFO "read %u bytes(s) from %lu\n", count, p);
	}
	mutex_unlock(&dev->mutex);

	return ret;
}

static ssize_t globalmem_write(struct file *filp, const char __user * buf,
			       size_t size, loff_t * ppos)
{
	unsigned long p = *ppos;
	unsigned int count = size;
	int ret = 0;
	struct globalmem_dev *dev = filp->private_data;

	if (p >= GLOBALMEM_SIZE)
		return 0;
	if (count > GLOBALMEM_SIZE - p)
		count = GLOBALMEM_SIZE - p;

	mutex_lock(&dev->mutex);

	if (copy_from_user(dev->mem + p, buf, count))
		ret = -EFAULT;
	else {
		*ppos += count;
		ret = count;

		printk(KERN_INFO "written %u bytes(s) from %lu\n", count, p);
	}
	mutex_unlock(&dev->mutex);

	return ret;
}

static loff_t globalmem_llseek(struct file *filp, loff_t offset, int orig)
{
	loff_t ret = 0;
	switch (orig) {
	case 0:
		if (offset < 0) {
			ret = -EINVAL;
			break;
		}
		if ((unsigned int)offset > GLOBALMEM_SIZE) {
			ret = -EINVAL;
			break;
		}
		filp->f_pos = (unsigned int)offset;
		ret = filp->f_pos;
		break;
	case 1:
		if ((filp->f_pos + offset) > GLOBALMEM_SIZE) {
			ret = -EINVAL;
			break;
		}
		if ((filp->f_pos + offset) < 0) {
			ret = -EINVAL;
			break;
		}
		filp->f_pos += offset;
		ret = filp->f_pos;
		break;
	default:
		ret =  - EINVAL;
		break;
	}
	return ret;
}

static const struct file_operations globalmem_fops = {
	.owner = THIS_MODULE,
	.llseek = globalmem_llseek,
	.read = globalmem_read,
	.write = globalmem_write,
	.unlocked_ioctl = globalmem_ioctl,
	.open = globalmem_open,
	.release = globalmem_release,
};

static void globalmem_setup_cdev(struct globalmem_dev *dev, int index)
{
	int err, devno = MKDEV(globalmem_major, index);

	cdev_init(&dev->cdev, &globalmem_fops);
	dev->cdev.owner = THIS_MODULE;
	err = cdev_add(&dev->cdev, devno, 1);
	if (err)
		printk(KERN_NOTICE "Error %d adding globalmem%d", err, index);
}

static int __init globalmem_init(void)
{
	int ret;
	dev_t devno = MKDEV(globalmem_major, 0);

	if (globalmem_major)
		ret = register_chrdev_region(devno, 1, "globalmem");
	else {
		ret = alloc_chrdev_region(&devno, 0, 1, "globalmem");
		globalmem_major = MAJOR(devno);
	}
	if (ret < 0)
		return ret;

	globalmem_devp = kzalloc(sizeof(struct globalmem_dev), GFP_KERNEL);
	if (!globalmem_devp) {
		ret = -ENOMEM;
		goto fail_malloc;
	}

	globalmem_setup_cdev(globalmem_devp, 0);
	mutex_init(&globalmem_devp->mutex);
	return 0;

 fail_malloc:
	unregister_chrdev_region(devno, 1);
	return ret;
}
module_init(globalmem_init);

static void __exit globalmem_exit(void)
{
	cdev_del(&globalmem_devp->cdev);
	kfree(globalmem_devp);
	unregister_chrdev_region(MKDEV(globalmem_major, 0), 1);
}
module_exit(globalmem_exit);

MODULE_AUTHOR("Barry Song <[email protected]>");
MODULE_LICENSE("GPL v2");

第8章 Linux設備驅動中的阻塞與非阻塞I/O
8.1 阻塞與非阻塞I/O
阻塞操作是指在執行設備操作時, 若不能獲得資源, 則掛起進程直到滿足可操作的條件後再進行操
作。 被掛起的進程進入睡眠狀態, 被從調度器的運行隊列移走, 直到等待的條件被滿足。 而非阻塞操作的
進程在不能進行設備操作時, 並不掛起, 它要麼放棄, 要麼不停地查詢, 直至可以進行操作爲止。
char buf;
fd = open("/dev/ttyS1", O_RDWR);
...
res = read(fd,&buf,1); /* 當串口上有輸入時才返回 */
if(res==1)
printf("%c\n", buf);

char buf;
fd = open("/dev/ttyS1", O_RDWR| O_NONBLOCK);
...
while(read(fd,&buf,1)!=1)
continue; /* 串口上無輸入也返回, 因此要循環嘗試讀取串口 */
printf("%c\n", buf);

8.2 輪詢操作
當多路複用的文件數量龐大、 I/O流量頻繁的時候, 一般不太適合使用select() 和poll() , 此種情
況下, select() 和poll() 的性能表現較差, 我們宜使用epoll。 epoll的最大好處是不會隨着fd的數目增長
而降低效率, select() 則會隨着fd的數量增大性能下降明顯
int epoll_create(int size);

創建一個epoll的句柄, size用來告訴內核要監聽多少個fd。 需要注意的是, 當創建好epoll句柄後, 它
本身也會佔用一個fd值, 所以在使用完epoll後, 必須調用close() 關閉。
int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);
告訴內核要監聽什麼類型的事件。 第1個參數是epoll_create() 的返回值, 第2個參數表示動作, 包
含:
EPOLL_CTL_ADD: 註冊新的fd到epfd中。
EPOLL_CTL_MOD: 修改已經註冊的fd的監聽事件。
EPOLL_CTL_DEL: 從epfd中刪除一個fd。
第3個參數是需要監聽的fd, 第4個參數是告訴內核需要監聽的事件類型, struct epoll_event結構如下:
struct epoll_event {
__uint32_t events; /* Epoll events */
epoll_data_t data; /* User data variable */
};
events可以是以下幾個宏的“或”:EPOLLIN: 表示對應的文件描述符可以讀。
EPOLLOUT: 表示對應的文件描述符可以寫。
EPOLLPRI: 表示對應的文件描述符有緊急的數據可讀( 這裏應該表示的是有socket帶外數據到
來) 。
EPOLLERR: 表示對應的文件描述符發生錯誤。
EPOLLHUP: 表示對應的文件描述符被掛斷。
EPOLLET: 將epoll設爲邊緣觸發( Edge Triggered) 模式, 這是相對於水平觸發( Level Triggered) 來
說的。 LT( Level Triggered) 是缺省的工作方式, 在LT情況下, 內核告訴用戶一個fd是否就緒了, 之後用
戶可以對這個就緒的fd進行I/O操作。 但是如果用戶不進行任何操作, 該事件並不會丟失, 而ET( EdgeTriggered) 是高速工作方式, 在這種模式下, 當fd從未就緒變爲就緒時, 內核通過epoll告訴用戶, 然後它
會假設用戶知道fd已經就緒, 並且不會再爲那個fd發送更多的就緒通知。
EPOLLONESHOT: 意味着一次性監聽, 當監聽完這次事件之後, 如果還需要繼續監聽這個fd的話,
需要再次把這個fd加入到epoll隊列裏。
int epoll_wait(int epfd, struct epoll_event * events, int maxevents, int timeout);
等待事件的產生, 其中events參數是輸出參數, 用來從內核得到事件的集合, maxevents告訴內核本次
最多收多少事件, maxevents的值不能大於創建epoll_create( ) 時的size, 參數timeout是超時時間( 以毫秒
爲單位, 0意味着立即返回, -1意味着永久等待) 。 該函數的返回值是需要處理的事件數目, 如返回0, 則
表示已超時。
一般來說, 當涉及的fd數量較少的時候, 使用select是合適的; 如果涉及的fd很多, 如在大規模併發的服務器中偵聽許多socket
的時候, 則不太適合選用select, 而適合選用epoll。

第9章 Linux設備驅動中的異步通知與異步I/O
在設備驅動中使用異步通知可以使得在進行對設備的訪問時, 由驅動主動通知應用程序進行訪問。 這
樣, 使用非阻塞I/O的應用程序無須輪詢設備是否可訪問, 而阻塞訪問也可以被類似“中斷”的異步通知所
取代。
除了異步通知以外, 應用還可以在發起I/O請求後, 立即返回。 之後, 再查詢I/O完成情況, 或者I/O完
成後被調回。 這個過程叫作異步I/O。
 

異步通知的意思是: 一旦設備就緒, 則主動通知應用程序, 這樣應用程序根本就不需要查詢設備狀
態, 這一點非常類似於硬件上“中斷”的概念, 比較準確的稱謂是“信號驅動的異步I/O”。 信號是在軟件層
次上對中斷機制的一種模擬, 在原理上, 一個進程收到一個信號與處理器收到一箇中斷請求可以說是一樣
的。 信號是異步的, 一個進程不必通過任何操作來等待信號的到達, 事實上, 進程也不知道信號到底什麼
時候到達。
阻塞I/O意味着一直等待設備可訪問後再訪問, 非阻塞I/O中使用poll() 意味着查詢設備是否可訪
問, 而異步通知則意味着設備通知用戶自身可訪問, 之後用戶再進行I/O處理。 由此可見, 這幾種I/O方式
可以相互補充
第10章 中斷與時鐘
10.1 中斷與定時器
中斷是指CPU在執行程序的過程中, 出現了某些突發事件急待處理, CPU必須暫停當前程序的執
行, 轉去處理突發事件, 處理完畢後又返回原程序被中斷的位置繼續執行。
內部中斷的中斷源來自CPU內部(軟件中斷指
令、 溢出、 除法錯誤等, 例如, 操作系統從用戶態切換到內核態需藉助CPU內部的軟件中斷) ,外部中斷
的中斷源來自CPU外部, 由外設提出請求
中斷是否可以屏蔽, 中斷可分爲可屏蔽中斷與不可屏蔽中斷(NMI) , 可屏蔽中斷可以通過設置
中斷控制器寄存器等方法被屏蔽, 屏蔽後, 該中斷不再得到響應, 而不可屏蔽中斷不能被屏蔽。
根據中斷入口跳轉方法的不同, 中斷可分爲向量中斷和非向量中斷。 採用向量中斷的CPU通常爲不同
的中斷分配不同的中斷號, 當檢測到某中斷號的中斷到來後, 就自動跳轉到與該中斷號對應的地址執行。
不同中斷號的中斷有不同的入口地址。 非向量中斷的多箇中斷共享一個入口地址, 進入該入口地址後, 再
通過軟件判斷中斷標誌來識別具體是哪個中斷。 也就是說, 向量中斷由硬件提供中斷服務程序入口地址,
非向量中斷由軟件提供中斷服務程序入口地址。
10.2 Linux中斷處理程序架構
a.中斷執行時間儘量短

b.中斷處理需完成的工作儘量大

10.3 Linux中斷編程
10.3.1 申請和釋放中斷
在Linux設備驅動中, 使用中斷的設備需要申請和釋放對應的中斷, 並分別使用內核提供的
request_irq() 和free_irq() 函數。
1.申請irq
int request_irq(unsigned int irq, irq_handler_t handler, unsigned long flags,
const char *name, void *dev);
irq是要申請的硬件中斷號。
handler是向系統登記的中斷處理函數(頂半部) , 是一個回調函數, 中斷髮生時, 系統調用這個函
數, dev參數將被傳遞給它。
irqflags是中斷處理的屬性, 可以指定中斷的觸發方式以及處理方式。 在觸發方式方面, 可以是
IRQF_TRIGGER_RISING、 IRQF_TRIGGER_FALLING、 IRQF_TRIGGER_HIGH、 IRQF_TRIGGER_LOW
等。 在處理方式方面, 若設置了IRQF_SHARED, 則表示多個設備共享中斷, dev是要傳遞給中斷服務程
序的私有數據, 一般設置爲這個設備的設備結構體或者NULL。
request_irq() 返回0表示成功, 返回-EINVAL表示中斷號無效或處理函數指針爲NULL, 返回-
EBUSY表示中斷已經被佔用且不能共享。

2.釋放irq
與request_irq() 相對應的函數爲free_irq() , free_irq() 的原型爲:
void free_irq(unsigned int irq,void *dev_id);
free_irq() 中參數的定義與request_irq() 相同。
10.3.2 使能和屏蔽中斷
下列3個函數用於屏蔽一箇中斷源:
void disable_irq(int irq);
void disable_irq_nosync(int irq);
void enable_irq(int irq);

10.3.3 底半部機制
Linux實現底半部的機制主要有tasklet、 工作隊列、 軟中斷和線程化irq。
 

/* 定義tasklet和底半部函數並將它們關聯 */
void xxx_do_tasklet(unsigned long);
DECLARE_TASKLET(xxx_tasklet, xxx_do_tasklet, 0);
/* 中斷處理底半部 */
void xxx_do_tasklet(unsigned long)
{
 ...
}
/* 中斷處理頂半部 */
irqreturn_t xxx_interrupt(int irq, void *dev_id)
{
 ...
 tasklet_schedule(&xxx_tasklet);
 ...
}
/* 設備驅動模塊加載函數 */
int __init xxx_init(void)
{
 ...
 /* 申請中斷 */
 result = request_irq(xxx_irq, xxx_interrupt,
 0, "xxx", NULL);
 ...
 return IRQ_HANDLED;
}
/* 設備驅動模塊卸載函數 */
void __exit xxx_exit(void)
{
 ...
 /* 釋放中斷 */
 free_irq(xxx_irq, xxx_interrupt);36 ...
}

 

/* 定義工作隊列和關聯函數 */
struct work_struct xxx_wq;
void xxx_do_work(struct work_struct *work);

/* 中斷處理底半部 */
void xxx_do_work(struct work_struct *work)
{
 ...
}

/*中斷處理頂半部*/
irqreturn_t xxx_interrupt(int irq, void *dev_id)
{
 ...
 schedule_work(&xxx_wq);
 ...
 return IRQ_HANDLED;
}

/* 設備驅動模塊加載函數 */
int xxx_init(void)
{
 ...
 /* 申請中斷 */
 result = request_irq(xxx_irq, xxx_interrupt,
 0, "xxx", NULL);
 ...
 /* 初始化工作隊列 */
 INIT_WORK(&xxx_wq, xxx_do_work);
 ...31}

/* 設備驅動模塊卸載函數 */
void xxx_exit(void)
{
36 ...
37 /* 釋放中斷 */
38 free_irq(xxx_irq, xxx_interrupt);
39 ...
40}

工作隊列的使用方法和tasklet非常相似, 但是工作隊列的執行上下文是內核線程, 因此可以調度和睡
眠。
 

10.4 中斷共享
 

代碼清單10.8 共享中斷編程模板
/* 中斷處理頂半部 */
irqreturn_t xxx_interrupt(int irq, void *dev_id)
{
 ...
 int status = read_int_status(); /* 獲知中斷源 */
 if(!is_myint(dev_id,status)) /* 判斷是否爲本設備中斷 */
 return IRQ_NONE; /* 不是本設備中斷, 立即返回 */

/* 是本設備中斷, 進行處理 */
 ...
 return IRQ_HANDLED; /* 返回IRQ_HANDLED表明中斷已被處理 */
}

/* 設備驅動模塊加載函數 */
int xxx_init(void)
{
 ...
 /* 申請共享中斷 */
 result = request_irq(sh_irq, xxx_interrupt,
 IRQF_SHARED, "xxx", xxx_dev);
 ...
}

/* 設備驅動模塊卸載函數 */
void xxx_exit(void)
{27 ...
 /* 釋放中斷 */
 free_irq(xxx_irq, xxx_interrupt);
 ...
}

10.5 內核定時器
軟件意義上的定時器最終依賴硬件定時器來實現, 內核在時鐘中斷髮生後檢測各定時器是否到期, 到
期後的定時器處理函數將作爲軟中斷在底半部執行。 實質上, 時鐘中斷處理程序會喚起TIMER_SOFTIRQ
軟中斷, 運行當前處理器上到期的所有定時器。
 

內核定時器使用模板
/* xxx設備結構體 */
struct xxx_dev {
 struct cdev cdev;
 ...
 timer_list xxx_timer; /* 設備要使用的定時器 */
};

/* xxx驅動中的某函數 */
xxx_func1(…)
{
 struct xxx_dev *dev = filp->private_data;
 ...
 /* 初始化定時器 */
 init_timer(&dev->xxx_timer);
 dev->xxx_timer.function = &xxx_do_timer;
 dev->xxx_timer.data = (unsigned long)dev;
 /* 設備結構體指針作爲定時器處理函數參數 */
 dev->xxx_timer.expires = jiffies + delay;
 /* 添加(註冊) 定時器 */
 add_timer(&dev->xxx_timer);
 ...
}

/* xxx驅動中的某函數 */
xxx_func2(…)
{
 ...
 /* 刪除定時器 */
 del_timer (&dev->xxx_timer);
 ...
}

/* 定時器處理函數 */
static void xxx_do_timer(unsigned long arg)
{
 struct xxx_device *dev = (struct xxx_device *)(arg);
 ...
 /* 調度定時器再執行 */
 dev->xxx_timer.expires = jiffies + delay;
 add_timer(&dev->xxx_timer);
 ...
}

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章