Linux驅(qū)動基礎(chǔ)知識筆記

一、入門

創(chuàng)新互聯(lián)是專業(yè)的田陽網(wǎng)站建設(shè)公司,田陽接單;提供成都網(wǎng)站設(shè)計、網(wǎng)站制作,網(wǎng)頁設(shè)計,網(wǎng)站設(shè)計,建網(wǎng)站,PHP網(wǎng)站建設(shè)等專業(yè)做網(wǎng)站服務(wù);采用PHP框架,可快速的進行田陽網(wǎng)站開發(fā)網(wǎng)頁制作和功能擴展;專業(yè)做搜索引擎喜愛的網(wǎng)站,專業(yè)的做網(wǎng)站團隊,希望更多企業(yè)前來合作!

1、字符設(shè)備驅(qū)動

1)注冊字符設(shè)備

static?inline?int?register_chrdev(unsigned?int?major,?const?char?*name,
??const?struct?file_operations?*fops);

2)cdev_add 其實1)調(diào)用了cdev_add

int?cdev_add(struct?cdev?*p,?dev_t?dev,?unsigned?count);
/*?調(diào)用關(guān)系?*/
register_chrdev
????__register_chrdev
????????cdev_add

2、用戶空間和內(nèi)核空間的數(shù)據(jù)拷貝

1)copy_to_user/copy_from_user://拷貝一個空間
static?__always_inline?unsigned?long?__must_check?copy_to_user(void?__user?*to,?const?void?*from,?unsigned?long?n);
static?__always_inline?unsigned?long?__must_checkcopy_from_user(void?*to,?const?void?__user?*from,?unsigned?long?n)
2)put_user(x,p)/get_user://從p指針傳單個值
#define?put_user(x,?ptr)					\
({								\
	void?__user?*__p?=?(ptr);				\
	might_fault();						\
	access_ok(VERIFY_WRITE,?__p,?sizeof(*ptr))??		\
		__put_user((x),?((__typeof__(*(ptr))?__user?*)__p))?:	\
		-EFAULT;					\
})

3、檢測用戶傳來的空間是不是合法

access_ok(int?,const?void?*addr,ulong)
ex:if(!access_ok(verify_write,buffer,count))return?error;

4、異步通知

fasync_helper(int?fd,struct?file,int?on,struct?fasync_struct?**);//on?0表示去除異步通知,1表示添加異步通知
kill_fasync(stuct?fasync_struct?**fp,int?sig,int?band);//當時間到達,將用來通知相關(guān)的進程

5、/proc

??通過它可以在運行時訪問內(nèi)核的內(nèi)部數(shù)據(jù)結(jié)構(gòu),改變內(nèi)核設(shè)置,通過它發(fā)送信息。ps、top命令就是通過讀取/PROC下的文件來后去信息。

一般情況proc自動加載,如果啟動沒有自動加載,可以用:mount -t proc proc /proc

內(nèi)核還提供了一些/proc文件系統(tǒng)的接口函數(shù):proc_mkdir;proc_create;proc_create_data;proc_remove;remove_proc_entry;

struct?proc_dir_entry?*proc_mkdir(const?char?*name,struct?proc_dir_entry?*parent);

6、內(nèi)核makefile

kbuild Makefile

obj-y表示連接進內(nèi)核,obj-m表示編譯成可以加載的模塊

1)目標定義

obj-(CONFIG_I2C_BOARDINFO)+=i2c-boardinfo.o

2)多文件模塊定義

obj-(CONFIG_FB)+=fb.o
fb-y:=fbmem.o?fbmon.o.....
fb-objs:=$(fb-y)

3)目錄迭代

obj-$(CONFIG_FB_OMAP)+=OMAP/

如果CONFIG_FB_OMAP的值是y或者m,kbuid會將omap目錄列入向相下迭代的目標中,但是其作用僅限于此,至于omap目錄下文件是要作為模塊編譯還是連接進入內(nèi)核,還要由omap目錄下的makefile文件的內(nèi)容來決定。

二、驅(qū)動模型

1、內(nèi)核對象

1)kobject(內(nèi)核對象,是內(nèi)核設(shè)備管理機制的最高的層抽象):一個kobject對應(yīng)sysfs文件系統(tǒng)一個目錄,還負責設(shè)備熱插拔等事件的處理工作。

對應(yīng)有一些接口函數(shù):kobject_init;kobject_add;將kobject加入到系統(tǒng);kobject_init_and_add;。。。等

void?kobject_init(struct?kobject?*kobj,?struct?kobj_type?*ktype);
int?kobject_add(struct?kobject?*kobj,?struct?kobject?*parent,const?char?*fmt,?...);

常見的kobject包括:

struct?kobject?*dev_kobh;//設(shè)備對象;
kobject?*sysfs_dev_char_kobj;//字符設(shè)備對象;
struct?kobject?*sysfs_dev_block_kobj;//塊設(shè)備對象;
struct?kobject?*kernel_kobj;//sysfs下的kernel對象。

2、內(nèi)核對象的類型:kobj_type{....sysfs_ops..};

sysfs_ops為內(nèi)核對象在sysyfs文件系統(tǒng)中的接口:show(kobject。。)顯示,store(kobject。。)存儲

3、kset kobject 通過kset組織層次化結(jié)構(gòu)

kset{
struct?list_head?list;//同一kset的鏈表
spinlock_t?list_lock;//鎖
struct?kobject?kobj;//自身的kobject
struct?kset_uevent_ops?*uenent_ops;//uevent?相關(guān)操作,如事件過濾
}

常見的kset包括:

struct?kset?*bus,*class,*system

4、設(shè)備模型層次:模型包括device、device_driver、bus、class(設(shè)備類型)

? 設(shè)備和設(shè)備總線均掛載在總線上,總線完成設(shè)備、設(shè)備驅(qū)動的匹配

? 使用class_create可以創(chuàng)建一個類,系統(tǒng)注冊的類可以在/sysfs/class目錄下找到

5、sysfs文件系統(tǒng)

? 系統(tǒng)中每個kobject對應(yīng)這sysyfs中的一個目錄,而每一個sysyfs中的目錄代表一個kobject對象,每個sysfs文件代表對應(yīng)kobject屬性。

sysfs文件系統(tǒng)最基本的函數(shù)包括:

sysfs_create_file創(chuàng)建文件,sysfs_create_dir_ns創(chuàng)建目錄等

static?inline?int?__must_check?sysfs_create_file(struct?kobject?*kobj,const?struct?attribute?*attr);

6、platform 平臺概念的引入能更好的描述設(shè)備的資源信息,例如總線地址、中斷、dma信息到呢個。也叫做虛擬總線。

7、attributes:設(shè)備、驅(qū)動、類均有自己的屬性,這些屬性在attribute結(jié)構(gòu)的基礎(chǔ)上,增加了顯示與存儲接口。

struct?attribute{
const?char?*name;
umode_t?mode;
#ifdef?CONFIG_DEBUG_LOCK_ALLOC
bool?ignore_lockdep:1;
struct?lock_classs?*key;
strct?loce_class_key?skey:
}

8、設(shè)備事件通知

1)kobject uevent 是內(nèi)核中東發(fā)送給應(yīng)用層設(shè)備事件。

kobject uevent包括?

enum?kobject_action?{
KOBJ_ADD,
KOBJ_REMOVE,
KOBJ_CHANGE,
KOBJ_MOVE,
KOBJ_ONLINE,
KOBJ_OFFLINE,
KOBJ_BIND,
KOBJ_UNBIND,
KOBJ_MAX
};

通過netlink機制,內(nèi)核通過kobject_uevent->kobject_uevent_env函數(shù)發(fā)送給netlink客戶端;

2)uevent helper

? 如果內(nèi)核支持uevent helper ,kobject_uevent_env就會調(diào)用應(yīng)用層的uevent helper程序

? Linux下的設(shè)備管理通常使用udev工具。mdev用來在嵌入式中替代udev。udev包含一個一直運行的后臺進程。與udev不同,mdev不是一直運行的后臺程序,它使用內(nèi)核喚醒,則mdev要被設(shè)置成uevent_helper程序。

3)udev

? 它是用來監(jiān)控udev客戶端的控制信息,內(nèi)核的hotplug事件,配置文件變化事件。當有設(shè)備插拔時,udev是會收到通知,它根據(jù)事件中參數(shù)和sysfs中的信息,調(diào)用合適的事件處理函數(shù),創(chuàng)建和刪除/dev節(jié)點。

? udev是通過netlink機制獲取內(nèi)核的uevent事件。mdev是通過直接訪問/sys/class/目錄來獲取設(shè)備信息。

? udev按照規(guī)則文件中的規(guī)則處理uevent事件,udev規(guī)則文件在目錄/etc/udev/rules.d下面。udev通過文件系統(tǒng)的inotify功能,監(jiān)控其規(guī)則文件目錄/etc/udev/rules.d,一旦該目錄下的規(guī)則文件變化,它就重新加載規(guī)則文件。udev規(guī)則文件中一個不以“#”開頭的行就是一條規(guī)則。每條規(guī)則包含匹配鍵和執(zhí)行鍵。配置鍵以“==”號與值連接;執(zhí)行用“=”

9、設(shè)備樹

? 設(shè)備樹用來描述板卡板級硬件信息。設(shè)備樹位于Linux內(nèi)核目錄代碼arch/arm/boot/dts下,dts文件為板級定義,dtsi危機為soc級定義。Linux設(shè)備樹編譯 make dtbs。

內(nèi)核啟動時會建立設(shè)備樹節(jié)點:

setup_arch
{
mdesc=setup_machine_fdt(__atags_pointer);//建立設(shè)備樹
unflagten_device_tree();//掃描設(shè)備樹,轉(zhuǎn)換成device_node
。。
}

? bootloader將設(shè)備樹的地址傳給內(nèi)核,放在R2寄存器中,在arch/arm/kernel/head-common.s文件同__mmap_switched賦值給__atags_pointer.

三、內(nèi)核同步機制

1、原子操作

typefef?stuct?(volatile?int?counter;)atomic_t;

volatile修飾符告訴編譯器不要對該類型的數(shù)據(jù)進行優(yōu)化

2、自旋鎖(一直循環(huán)直到條件滿足)導(dǎo)致cpu效率降低

spin_lock_init;
spin_lock;
spin_trylock;s
pin_unlock

spin_lock獲取成功立即返回,否則原地打轉(zhuǎn)。try函數(shù)嘗試獲取,如果立即獲取則返回真,否則返回假。

中斷安全的自旋鎖函數(shù):

硬件中斷

spin_lock_irq;
spin_unlock_irq;
spin_lock_irqsave;
spin_unlock_irqresore;

軟件中斷

spin_lock_bh;
spin_unlock_bh;

禁止本地cpu上的中斷與內(nèi)核搶占。save保存本地中斷狀態(tài),restore恢復(fù)中

3、讀寫鎖

讀寫鎖(rwlock)是一種特殊的自旋鎖。允許同時有多個讀者來訪問共享資源。一個讀寫鎖同時只能有一個寫著和多個讀者。

如果讀寫鎖當前沒有讀著也沒有寫著,寫者可以立即獲取讀寫鎖,否則自旋,直到?jīng)]有任何寫和讀著。如果讀寫鎖沒有寫者,那么讀者可以立即獲取讀寫鎖,否則自旋,直到寫著釋放該讀寫鎖。

rwlock_t?x;
rwlock_init(x);//動態(tài)初始化讀寫鎖
rwlock_t?x=RW+LOCK_UNLOCKED//靜態(tài)初始化

讀寫嘗試

read_lock;wirte_lock;read_trylock
read_unlock;wirte_unlock;?write_trylock

4、rcu(讀-復(fù)制-修改) 之使用于讀多寫少的情況。

原理是對于被rcu保護的共享數(shù)據(jù)機構(gòu),讀者不需要獲取任何鎖就可以訪問它,但寫者在訪問它時需要先復(fù)制一個副本,然后對副本進行修改,最后調(diào)用一個函數(shù)在合適的時機修改。就是所有引用數(shù)據(jù)的任務(wù)都退出。

讀者

#define?rcu_read_lock()?preempt_disable()?//進入讀操作臨界區(qū)標記
#define?rcu_read_unlock()?preempt_enable()?//退出讀操作臨界區(qū)

寫者一般對副本操作,然后將副本設(shè)定成正本,最后同步或者異步的釋放舊的。

struct?rcu_head{
struct?tcu_head*next;//下一個rcu_head
void?(*func)(stuct?rcu_head*);//獲取競爭條件后的處理函數(shù)
};

添加回調(diào)函數(shù) 同步rcu

void?call_rcu(struc?rcu_head*,rcu_callback_T?func);\?void?synchronize_rcu(void);

call_rcu函數(shù)調(diào)用后,直接返回,rcu軟中斷會調(diào)用回調(diào)汗死釋放舊的數(shù)據(jù)指針。sysnchronize_rcu函數(shù)則原地等待,它被喚醒時,即可釋放舊的數(shù)據(jù)指針。

5、信號量:是一種睡眠鎖。如果信號量被占用,信號量將將會將其調(diào)用者加入等待隊列。

? 自旋鎖和信號量的第一個區(qū)別:前者不引起調(diào)用者睡眠。自旋鎖和信號量的選用主要看鎖被持有的時間長短,如果短,就用自旋鎖。第二個區(qū)別:信號量有多個持有者,而自旋鎖只能有一個持有者。

sema_init(struct semaphore *sem,int val);down()down_trylock()down_interruptible(能被信號打斷);獲取,up()釋放,喚醒等待隊列

6、讀寫信號量:與讀寫鎖原理差不多。

7、互斥量:mutex,同一時間只允許一個訪問者,互斥量加鎖失敗會進入睡眠等待喚醒。

mutex_init(mutex);void mutex_lock(mutex*);;int mutex_trylock();void mutex_unlock();

8、等待隊列

? 等待隊列用于異步通知和阻塞式訪問。如果進程需要等待某些條件放生才能繼續(xù),則可以使用等待隊列機制。在Linux內(nèi)核中通常使用等待隊列來實現(xiàn)阻塞式訪問。

初始化一個等待隊列

void?init_waitqueue_head(wait_queue_head_t*q);

等待事件發(fā)生函數(shù):

wait_event(wq,condition)//不可中斷的等待
wait_event_interruptible(wq,condition)//可中斷的等待
wait_event_timeout(wq,condition,timeout)
wait_event_interruptible_timeout

喚醒等待隊列

wake_up(wait_queue_head_t,*Q);//喚醒所有等待q的進程
wake_up_interruptible(*Q);//只喚醒可以中斷休眠的進程

加入或退出等待隊列

add_wait_queue(wait_queue_head_t?*,wait_queue_t*)
add_wait_queue_exclusive
remove_wait_queue

加入等待隊列的線程將等待喚醒。阻塞式字符驅(qū)動一般讀函數(shù)中等待,并在中斷或內(nèi)核線程中使用wake_up函數(shù)喚醒等待隊列。

四、內(nèi)存管理和鏈表

1、物理地址和虛擬地址

? 如果cpu沒有mmu則發(fā)出的地址就是直接傳到芯片引腳,這個地址腳物理地址;如果有mmu,則發(fā)出的地址就是虛擬地址,mmu會將虛擬地址映射成物理地址。

? mmu將虛擬地址映射到物理地址是以頁為單位,對于32位cpu,通常一個頁4KB。物理內(nèi)存中的頁稱為物理頁面或者頁幀。mmu使用頁表來記錄虛擬地址頁面與物理內(nèi)存頁面之間的映射關(guān)系。

2、內(nèi)存分配

最長用的內(nèi)存申請和釋放函數(shù):

void?*kmalloc(size_t?size,gfp_t?flags);
void?*kzalloc(size_t?size,gfp_t?flags);//調(diào)用kmalloc分配內(nèi)存并將內(nèi)存清零
void?kfree(const?void*x);

Kmalloc函數(shù)分配的地址空間是線性映射的,它一般分配小于128kb的內(nèi)存。

flags GFP_KERNEL內(nèi)核空間進程使用。GFP_USER為用戶空間分配空間,GFP_HIGHUSER從高端地址分配 。。。等

如果要分配大塊內(nèi)存,應(yīng)使用面向頁的技術(shù)

unsigned?long?get_zeored_page(gfp_t?gfp_mask);//返回一個單個的,零填充的頁
unsigned?long?__get_free_pages(gfp_t?mask,unsigned?int?order);//直接獲取整頁的內(nèi)存(頁數(shù)是2?的冪)
free_page(addr,order);

如果需要申請一塊連續(xù)的虛擬地址內(nèi)存,物理地址不是連續(xù)的,頁表查詢比較頻繁,效率底:

void?*vmalloc(size);
void?*vmalloc_user(size);為用戶空間分配內(nèi)存
void?vfree(void?*addr);

3、cache

高速緩存。Linux使用slab機制管理cache。kmem_cache_create創(chuàng)建slab緩存。

kmem_cache_alloc//從cache中分配內(nèi)存
kmem_cache_free
kmem_cache_destroy//銷毀slab緩存

4、IO端口到虛擬地址映射

? arm中,外設(shè)I/0端口具有和內(nèi)存一樣的物理地址,外設(shè)的i/O內(nèi)存資源地址是已知的,有硬件的設(shè)計決定。Linux的驅(qū)動程序并不能直接通過物理地址訪問I/0內(nèi)存資源,而必須將物理地址轉(zhuǎn)換成虛擬地址。

1)靜態(tài)映射

? 在arm存儲系統(tǒng)中,使用mmu實現(xiàn)虛擬地址到物理地址的映射。mmu的實現(xiàn)過程,實際上就是一個查表映射的過程。建立頁表是實現(xiàn)mmu功能不可或缺的一步。頁表位于系統(tǒng)的內(nèi)存中,頁表的每一項對應(yīng)于一個虛擬地址到物理地址的映射。

Linux內(nèi)存的create_mapping函數(shù)創(chuàng)建線性映射表。

stuct?map_desc{
unsigned?ling?virtual;//虛擬地址
unsigned?long?pfn;//__phys_to_pfn(phy_addr)
unsiged?long?length;//長度
unsiged?int?type;
}
void?__init?create_mapping(struct?map_desc*md);
/*?例:
arm平臺使用iotable_init來創(chuàng)建平臺專用映射:*/
void?__init?iotable_init(struct?map_desc?*io_desc,int?nr);
?
static?struct?mcp_Desc?smdk6410_iodesc[]?=?{};//?需要建立的映射在此添加
s3c64xx_init_io(smdk6410_iodesc,ARRAY_SIZE(smdk6410_iodesc));
{
iotable_init(smdk6410_iodesc,ARRAY_SIZE(smdk6410_iodesc));;
..
..
..
}

2)ioremap

如果需要在模塊中動態(tài)映射IO,可以采用ioremap函數(shù)。此函數(shù)將i/o內(nèi)存資源的物理地址映射到核心虛擬地址空間。

typedef?phys_addr_t?resource_size_t;
void?__iomem?*ioremap(resource_size_t?res_cookie/*物理地址*/,size_t?size);
void?iounmap(volatile?void?__iomem?*iomem_cookie);//取消映射

例:

reserve_virt_addr=ioremap(100*1024*1024,10*1024*1024);//將101MB開始的10MB地址映射到虛擬地址。

5、內(nèi)核空間到用戶空間的映射

? mmap接口。將內(nèi)核地址映射到用戶地址,應(yīng)用程序可以直接訪問內(nèi)存地址。

? 系統(tǒng)調(diào)用

?unsigned?long?mmap(unsigned?long?addr,unsigned?long?len,int?prot,int?flags,int?fd,long?off);//取消映射munmap函數(shù)

驅(qū)動需要實現(xiàn)

memapmem_fops{
..
.mmap?=?memapmem_mmap;
}

例:

fd=open("/dev/mmap",O_RDWR);
addr=mmap(NULL,4096,PROT_READ|PROT_WRITE,MAP_SHARED,fd,0);

6、DMA映射

1)建立一致性DMA映射:dma_alloc_coherent(禁止頁表的Cacheable項和Bufferable)

2)建立非一致性DMA映射:dma_alloc_noncoherent

7、鏈表是雙向鏈表:可以雙向遍歷

五、任務(wù)和調(diào)度

1、schedule

? linux進程在等待資源就緒的過程中,可以主動讓出cpu,自身進入休眠狀態(tài),等待喚醒后繼續(xù)檢查資源是否就緒。進程可以調(diào)用schedule函數(shù)讓出cpu,進程被喚醒后將從schedule函數(shù)的下一條代碼開始執(zhí)行。

void?_sched?schedule(void)
signed?long?_sched?schedule_timeout(timeout)//帶超時的調(diào)度
例:
process?a:
set_current_state(TASK_INTERRUPTIBLE);
spin_lock(&list_lock);
if(list_empty(&list_head)){
spin_unlock(&list_lock);
schedule();
spin_lock(&list_lock);
}
set_current_state(SASK_RUNNING);
spin_unlock(&list_lock);
process?b:
spin_lock(&list_lock);
list_add_tail(&list_head,new_node);
spin_lock(&list_lock);
wake_up_process(process?a);

2、內(nèi)核線程kthread_create

? kthread_cretate創(chuàng)建的線程不能立馬運行,需要wake_up_process函數(shù)喚醒。kthread_run(先調(diào)用kthread_create,再調(diào)用wake_up_process)宏完成了kthread_create與wake_up_process兩步。? ? ? kthread_stop結(jié)束內(nèi)核線程,應(yīng)保證線程函數(shù)尚未結(jié)束,否則會一直等待。

3、內(nèi)核調(diào)用應(yīng)用程序

int?call_usermodehelper(char?*path,char?**argv,char?**envp,int?wait);

path程序路徑,argv參數(shù),envp環(huán)境變量,wait等待結(jié)束標志

4、軟中斷機制

1)原理

? 硬件中斷是硬件產(chǎn)生的中斷信號,軟中斷是軟件模擬的中斷。硬件產(chǎn)生中斷后,會將中斷通知給cpu,cpu查詢向量表將中斷映射成具體的程序。軟中斷完成在操作系統(tǒng)內(nèi)部,內(nèi)核運行一個守護進程來實現(xiàn)中斷查詢與執(zhí)行,這個線程的功能類似處理器的中斷控制器。構(gòu)成軟件中斷機制的核心元素包含:軟件中斷狀態(tài)(soft interrupt state)、軟中斷向量表(softirq_vec)、軟中斷線程(softirq thread)

? 系統(tǒng)在ksoftirqd內(nèi)核進程中調(diào)用__do_softirq循環(huán)檢測軟中斷是否處于pending狀態(tài),如果是,則執(zhí)行相應(yīng)處理函數(shù)。

? 在linux 4.5內(nèi)核最多可以有10中軟中斷,包括定時器、網(wǎng)絡(luò)軟中斷、tasklet。優(yōu)先級從0-9,對應(yīng)10 個已經(jīng)定義好的函數(shù)。

? 內(nèi)核將整個的中斷處理流程分為了上半部和下半部。上半部就是之前所說的中斷處理函數(shù),它能最快的響應(yīng)中斷,并且做一些必須在中斷響應(yīng)之后馬上要做的事情。而一些需要在中斷處理函數(shù)后繼續(xù)執(zhí)行的操作,內(nèi)核建議把它放在下半部執(zhí)行。

2)tasklet

? 軟中斷是利用軟件模擬的中斷機制,常用來執(zhí)行異步任務(wù)。tasklet是利用軟中斷實現(xiàn)的一種下半部機制。

? 軟中斷和tasklet優(yōu)先級較高,性能較好,調(diào)度快,但不能睡眠。而工作隊列是內(nèi)核的進程調(diào)度,相對來說較慢,但能睡眠。所以,如果你的下半部需要睡眠,那只能選擇工作隊列。否則最好用tasklet。

三個步驟:

(1)編寫tasklet處理程序
static?void?tasklet_callback(ulong?data);
(2)聲明tasklet
DECLEARE_TASKLET(tasklet,tasklet_callback,0);
(3)調(diào)度tasklet
static?irqreturn_t?irq_handler(int?irq,void?*arg)
{
tasklet_schedule(&tasklet);
return?IRQ_HANDLED;
}

5、工作隊列

1)原理

? 工作隊列類似tasklet,允許調(diào)用者請求在將來某一個時間調(diào)用一個函數(shù)。tasklet在軟中斷上下文中允許,所以tasklet執(zhí)行很快。工作隊列在一個特殊內(nèi)核進程上下文運行,有很多靈活性,并且能夠休眠。工作隊列包括一系列將要執(zhí)行的任務(wù)和執(zhí)行這些任務(wù)的內(nèi)核線程。每個工作隊列有一個專門的線程,所有的而任務(wù)必須在進程的上下文中運行,這樣可以安全的休眠。Linux提供一系列全局work queue,包含system_wq、system_highpri_wq等。驅(qū)動程序可以創(chuàng)建并使用他們自己的工作隊列。

2)延遲工作隊列:延遲工作隊列基于工作隊列,可以實現(xiàn)延遲一段時間再將工作加入到工作隊列

6、內(nèi)核時間

1)時間概念

(1)時鐘周期(clock cycle):晶振振蕩器在1s內(nèi)產(chǎn)生的時鐘脈沖個數(shù)。Linux用宏CLOCK_TICK_RATE來表示計數(shù)器的輸入時鐘脈沖的頻率。

(2)時鐘滴答(clock tick):一次時鐘中斷產(chǎn)生一次時鐘滴答。系統(tǒng)每個時鐘周期產(chǎn)生一次時鐘中斷。

(3)時鐘滴答頻率:1s內(nèi)的時鐘滴答次數(shù)。Linux內(nèi)核用HZ來表示時鐘滴答的頻率,而HZ通常就是1s。

(4)全局變量(jiffies):一個32為無符號整數(shù),用來表示自內(nèi)核上一次啟動以來的時鐘滴答次數(shù)。每滴答一次,內(nèi)核的時鐘中斷處理函數(shù)timer_interrupt會將該變量加1.

(5)xtime:timeval結(jié)構(gòu)全局變量,記載系統(tǒng)自開機以來的當前時間,基準為1970.1.1

(6)系統(tǒng)時鐘:也是軟件時鐘,由軟件根據(jù)時間中斷計時。

內(nèi)核可以應(yīng)下面函數(shù)獲取和設(shè)置系統(tǒng)時間:

void?do_gettimeofday(struct?timeval?*tv);int?do_settimeofday(struct?timespec?*tv)
timeval和timespec與jiffies轉(zhuǎn)換?timespec_to_jiffies;timeval_to_jiffies

2)Linux下的延遲

內(nèi)核定義了一堆宏來實現(xiàn)延遲:

#define?time_after(a,b)
#define?time_before
#define?time_after_eq(a,b)
?
#define?ndelay(n)//納秒
#define?udelay(n)//微秒
#define?mdealy(n)//毫秒

以上都是忙等待,會導(dǎo)致其他任務(wù)此時間無法使用cpu,下面是不必忙等待的短延遲方法:

void?msleep(u?int);ulong?msleep_interruptible(u?int);單位是milliseconds。

3)內(nèi)核定時器

timer_list{
struct?list_head?list;
ulong?ecpires;//定時器到期時間
ulong?data;//傳遞給處理函數(shù)的
void?(*fun)(ulong);//回調(diào)函數(shù)
}

操作:

增加:add_timer(timer_list *)

刪除:del_timer

修改ecpire值:mod_timer

六、簡單硬件設(shè)備驅(qū)動程序

1、處理器訪問硬件設(shè)備主要通過下面幾種方式:

(1)內(nèi)存方式。外設(shè)的內(nèi)存空間被映射到處理器的地址空間,處理器通過訪問映射地址來訪問硬件

(2)I/O接口。處理器與I/O設(shè)備之間通過一定的接口連接,這個接口就是I/O接口。I/O接口中包括一組寄存器以及控制電路。

(3)管腳(pin)。管腳可以用來對芯片進行復(fù)位,并接收來自設(shè)備的中斷信號。另外有些芯片還可以通過管腳進行簡單的模式配置。

? 在x86體系中,I/O地址空間與內(nèi)存地址空間是分開的,寄存器位于I/O空間是,稱為I/O端口。在arm等體系中,I/O通常是和內(nèi)存統(tǒng)一編制的,也稱為I/O內(nèi)存,是系統(tǒng)中訪問速度最快的內(nèi)存。

2、嵌入式Linux系統(tǒng)構(gòu)成

bootlader (傳參,設(shè)備樹(R2寄存器)等)-》kernel-》根文件系統(tǒng)-》其他文件系統(tǒng)掛載在根文件系統(tǒng)下面

3、硬件初始化

硬件初始化放在kernel下的arch目錄下,如arch/arm/mach-xxx/mach-xxxx.c

DT_MACHINE_START(LS1021A,?"Freescale?LS1021A")
.smp?=?smp_ops(ls1021a_smp_ops),
.dt_compat?=?ls1021a_dt_compat,
MACHINE_END

4、clk體系

時鐘就像人的心跳,沒有時鐘,外設(shè)就無法運行。時鐘相關(guān)代碼在/driver/clk

5、dev/mem與dev/kmem

/dev/mem是物理內(nèi)存的映射,可以用來訪問物理I/O設(shè)備,例如接口控制器的寄存器。/dev/kmem是虛擬內(nèi)存的映射,可以用來看下kernel的變量等信息。

例:

target = strtoul(argv[1],0,0);

打開內(nèi)存設(shè)備:

fd=open("/dev/mem",O_RDWR|O_SYNC);

映射一個頁面

map_base=mmcp(0,MAP_SIZE,PORT_READ|PORT_WRITE,MAP_SHARED,fd,target&~MAP_MASK);

根據(jù)數(shù)據(jù)類型獲取內(nèi)存的值

vir_addr?=?map_base+(target&map_mask);

然后就可以通過操作vir_addr來操作相應(yīng)的寄存器。

6、寄存器訪問

1)如S3C6410X處理器,支持32為物理地址空間,這些空間分為兩個部分,一部分用于存儲,一部分用于外設(shè)。

? 通過spine總線訪問主存,主存范圍i是0x00000000~0x6fffffff

引導(dǎo)鏡像區(qū):-0x07ffffff

內(nèi)部存儲區(qū):-0x0fffffff

靜態(tài)存儲區(qū):-0x3fffffff 用于訪問SROM,SRAM NOR FLASH

動態(tài)存儲區(qū):-0x6fffffff

? 外設(shè)區(qū)域通過peri總線訪問,范圍0X70000000-0X7FFFFFFF.

? Linux必須將外設(shè)的物理地址映射成虛擬地址才能使用。

? 地址映射可以采用固定地址映射

#define S3C_VA_IRQ S3C_ADDR(0X00000000) /*irq控制器*/

? 另一種方式采用ioremap函數(shù)。

? 當I/O寄存器與內(nèi)存統(tǒng)一編址時,I/O寄存器也稱I/O內(nèi)存。當I/O寄存器與內(nèi)存分開編址時,I/O寄存器也稱I/O端口。在I/O內(nèi)存資源地址映射成虛擬地址后,為了保證驅(qū)動程序的跨平臺性,應(yīng)該使用Linux中特定的函數(shù)訪問I/O內(nèi)存資源,而不應(yīng)該通過指向虛擬地址的指針來訪問。

void?writew(u16,volatile?void?__iomem*addr);
void?iowrite16(u16,void?__iomem*addr);
void?iorwrite16_rep(const?volatile?void?__iomem*addr,void?*buffer,uint?cont);//連續(xù)的

2)看門狗

為保證系統(tǒng)出現(xiàn)異常時能自動啟動,處理器均提供了看門狗功能??撮T狗單元即可以產(chǎn)生復(fù)位信號,也可以被用作一個普通的16位間隔定時器來產(chǎn)生中斷服務(wù)。

看門狗寄存器

WTCON 0x7e004000 r/w 看門狗定時器控制寄存器

WTDAT 0X7E004004 R/W 看門狗定時器數(shù)據(jù)寄存器

WTCNT 0X7E004008 R/W 看門狗計數(shù)器計數(shù)控制器

WTCLRINT 0X7E00400C W 中斷清除寄存器

WTDAT 保存看門狗定時器重載計數(shù)值。WTCNT保存看門狗定時器當前的值。WTCLRINT 用來清除看門狗定時間中斷,寫入任意值將清除中斷。

7、電平控制

? 一般電平包括高、底電平兩種。常用的電平包括TTL電平、CMOS電平和RS232電平,各種電平的電壓范圍不同,TTL電平信號+5V等價于邏輯1,0V等價于0.一般輸入,<1.2V為低電平,>2.0V為高,輸出,<0.8低,>2.4高。電平控制離不開GPIO控制。

8、硬件中斷處理

? 由硬件產(chǎn)生的一種電信號,并直接送入中斷控制器輸入引腳,再由中斷控制器向處理器發(fā)送相應(yīng)的信號。

如果中斷處理過程非常復(fù)雜,可以分成兩個部分:上半部和下半部。上半部完成一些緊急事物,下半部完成剩余的事物。上半部不可以中斷,下半部可以。Linux中的下半部包括軟中斷、tasklet機制和工作隊列、定時器等。

發(fā)生中斷時:

cpu跳到"vector_irq", 保存現(xiàn)場, 調(diào)用C函數(shù)handle_arch_irq

handle_arch_irq:

a. 讀 int controller, 得到hwirq

b. 根據(jù)hwirq得到virq

c. 調(diào)用 irq_desc[virq].handle_irq

? 驅(qū)動注冊中斷處理函數(shù):驅(qū)動程序 request_irq(virq, my_handler)

9、看門狗驅(qū)動框架

在Linux/drivers/watchdog目錄。

看門狗設(shè)備結(jié)構(gòu)

struct watchdog_device

注冊與注銷看門狗:int watchdog_register_device(watchdog_device *);void watchdog_unregister_device();

看門狗有一個重要的參數(shù),就是看門狗操作:

struct?watchdog_ops{
int?(*start)(struct?watchdog_device*);
...
}

watchdog_register_device會調(diào)用一個雜項設(shè)備驅(qū)動,注冊一個字符設(shè)備驅(qū)動。

例:

static?const?struct?watchdog_info?s3c2410_wdt_ident?=?{
.options?=?OPTIONS,
.firmware_version?=?0,
.identity?=?"S3C2410?Watchdog",
};
static?const?struct?watchdog_ops?s3c2410wdt_ops?=?{

.owner?=?THIS_MODULE,
.start?=?s3c2410wdt_start,
.stop?=?s3c2410wdt_stop,
.ping?=?s3c2410wdt_keepalive,
.set_timeout?=?s3c2410wdt_set_heartbeat,
.restart?=?s3c2410wdt_restart,
};
static?const?struct?watchdog_device?s3c2410_wdd?=?{
.info?=?&s3c2410_wdt_ident,
.ops?=?&s3c2410wdt_ops,
.timeout?=?S3C2410_WATCHDOG_DEFAULT_TIME,
};
watchdog_register_device(&wdt->wdt_device);注冊

10、RTC驅(qū)動

? 嵌入式系統(tǒng)一般有兩個時間,一個是RTC時間,一個是Linux系統(tǒng)時間。RTC時間存儲在RTC控制器中,系統(tǒng)斷電后通過電池供電,保證系統(tǒng)下次重新上電都能讀到正確的時間。通常在系統(tǒng)啟動腳本中讀取RTC時間,并將RTC時間設(shè)置為系統(tǒng)時間。Linux中的date命令是用來讀取和設(shè)置系統(tǒng)時間;而hwclock命令是用來讀取和設(shè)置RTC時間的。

注冊與注銷RTC驅(qū)動

devm_rtc_device_register(&pdev->dev,?"s3c",?&s3c_rtcops,THIS_MODULE);

RTC設(shè)備類的操作函數(shù)接口

struct?rtc_class_ops?{
int?(*ioctl)(struct?device?*,?unsigned?int,?unsigned?long);
int?(*read_time)(struct?device?*,?struct?rtc_time?*);
int?(*set_time)(struct?device?*,?struct?rtc_time?*);
int?(*read_alarm)(struct?device?*,?struct?rtc_wkalrm?*);
int?(*set_alarm)(struct?device?*,?struct?rtc_wkalrm?*);
int?(*proc)(struct?device?*,?struct?seq_file?*);
int?(*set_mmss64)(struct?device?*,?time64_t?secs);
int?(*set_mmss)(struct?device?*,?unsigned?long?secs);
int?(*read_callback)(struct?device?*,?int?data);
int?(*alarm_irq_enable)(struct?device?*,?unsigned?int?enabled);
int?(*read_offset)(struct?device?*,?long?*offset);
int?(*set_offset)(struct?device?*,?long?offset);
};

RTC驅(qū)動也包含一個通用的設(shè)備層,負責創(chuàng)建/dev/trc設(shè)備,并向應(yīng)用層提供統(tǒng)一接口(調(diào)用devm_rtc_device_register注冊RTC,該函數(shù)會調(diào)用創(chuàng)建設(shè)備節(jié)點函數(shù))

11、LED類設(shè)備

? Linux 內(nèi)核定義了LED類設(shè)備專門的處理各種外設(shè)的LED燈。

struct?led_classdev{
?..
}
#define?led_classdev_register(parent,?led_cdev)?\
??of_led_classdev_register(parent,?NULL,?led_cdev)
void?led_classdev_unregister(struct?led_classdev?*led_cdev)

網(wǎng)站欄目:Linux驅(qū)動基礎(chǔ)知識筆記
網(wǎng)站鏈接:http://bm7419.com/article40/geiceo.html

成都網(wǎng)站建設(shè)公司_創(chuàng)新互聯(lián),為您提供小程序開發(fā)定制網(wǎng)站、App開發(fā)、服務(wù)器托管、ChatGPT

廣告

聲明:本網(wǎng)站發(fā)布的內(nèi)容(圖片、視頻和文字)以用戶投稿、用戶轉(zhuǎn)載內(nèi)容為主,如果涉及侵權(quán)請盡快告知,我們將會在第一時間刪除。文章觀點不代表本網(wǎng)站立場,如需處理請聯(lián)系客服。電話:028-86922220;郵箱:631063699@qq.com。內(nèi)容未經(jīng)允許不得轉(zhuǎn)載,或轉(zhuǎn)載時需注明來源: 創(chuàng)新互聯(lián)

微信小程序開發(fā)