版權(quán)說明:本文檔由用戶提供并上傳,收益歸屬內(nèi)容提供方,若內(nèi)容存在侵權(quán),請(qǐng)進(jìn)行舉報(bào)或認(rèn)領(lǐng)
文檔簡(jiǎn)介
精品文檔-下載后可編輯linuxUART串口驅(qū)動(dòng)開發(fā)文檔-基礎(chǔ)電子w83697/w83977superI/O串口驅(qū)動(dòng)開發(fā)
內(nèi)容簡(jiǎn)介:介紹了Linux下的串口驅(qū)動(dòng)的設(shè)計(jì)層次及接口,并指出串口與TTY終端之間的關(guān)聯(lián)層次(串口可作TTY終端使用),以及Linux下的中斷處理機(jī)制/中斷共享機(jī)制,還有串口緩沖機(jī)制當(dāng)中涉及的軟中斷機(jī)制;其中有關(guān)w83697/w83977IC方面的知識(shí),具體參考相關(guān)手冊(cè),對(duì)串口的配置寄存器有詳細(xì)介紹,本文不再進(jìn)行說明.
目錄索引:
一.Linux的串口接口及層次.
二.Linux的中斷機(jī)制及中斷共享機(jī)制.
三.Linux的軟中斷機(jī)制.
四.TTY與串口的具體關(guān)聯(lián).
一.Linux的串口接口及層次.
串口是使用已經(jīng)非常廣的設(shè)備了,因此在linux下面的支持已經(jīng)很完善了,具有統(tǒng)一的編程接口,驅(qū)動(dòng)開發(fā)者所要完整的工作就是針對(duì)不同的串口IC來做完成相應(yīng)的配置宏,這此配置宏包括讀與寫,中斷打開與關(guān)閉(如傳送與接收中斷),接收狀態(tài)處理,有FIFO時(shí)還要處理FIFO的狀態(tài).如下我們就首先切入這一部分,具體了解一下與硬件串口IC相關(guān)的部分在驅(qū)動(dòng)中的處理,這一部分可以說是串口驅(qū)動(dòng)中的基礎(chǔ)部分,直接與硬件打交道,完成層具體的串口數(shù)據(jù)傳輸.
1.串口硬件資源的處理.
W83697及W83977在ep93xx板子上的映射的硬件物理空間如下:
W83697:0x20000000起1K空間.
W83977:0x30000000起1K空間.
因?yàn)榇谠O(shè)備的特殊性,可以當(dāng)作終端使用,但是終端的使用在內(nèi)核還未完全初始化之前(關(guān)于串口與終端的關(guān)聯(lián)及層次在第四節(jié)中詳細(xì)),此時(shí)還沒有通過mem_init()建立內(nèi)核的虛存管理機(jī)制,所以不能通過ioreamp來進(jìn)行物理內(nèi)存到虛存的映射(物理內(nèi)存必須由內(nèi)核映射成系統(tǒng)管理的虛擬內(nèi)存后才能進(jìn)行讀寫訪問),這與先前所講的framebuffer的物理內(nèi)存映射是不同的,具體原因如下:
√終端在注冊(cè)并使用的調(diào)用路徑如下:
start_kernel→console_init→uart_console_init→ep93xxuart_console_init→register_console→csambuart_console_write.
√FrameBuffer顯卡驅(qū)動(dòng)中的物理內(nèi)存映射調(diào)用路徑如下:
start_kernel→rest_init→init(內(nèi)核初始線程)→do_basic_setup→do_initcalls→fbmem_init→lanrryfb_init
(Linux下用__setup啟動(dòng)初期初始機(jī)制與__initcall系統(tǒng)初始化完成后的調(diào)用機(jī)制,這兩個(gè)機(jī)制本質(zhì)沒有什么差別,主要是執(zhí)行時(shí)所處的系統(tǒng)時(shí)段)
√串口物理內(nèi)存映射到虛存的時(shí)機(jī):
依據(jù)上面所介紹的兩條執(zhí)行路徑,再看內(nèi)核的內(nèi)存初始化的調(diào)用時(shí)期,只有完成這個(gè)初始化后才能進(jìn)行物理內(nèi)存到虛存的映射,內(nèi)存的初始化主要是在start_kernel中調(diào)用的mem_init,這個(gè)調(diào)用明顯在uart_console_init之后,在fbmem_init之后,到此就全部說明了為何不能在對(duì)串口使用ioremap進(jìn)行物理內(nèi)存的映射了。那么究竟要在什么時(shí)機(jī)用什么方法進(jìn)行串口物理內(nèi)存的映射呢?
√串口物理內(nèi)存的映射方式:
參考ep93xx的板載I/O的映射處理,它的處理方式是性將所有的物理I/O所在的內(nèi)存空間映射到虛存空間,映射的基址是IO_BASE_VIRT,大小是IO_SIZE.
/*WhereinvirtualmemorytheIOdevices(timers,systemcontrollers
*andsoon).Thisgetsusedinarch/arm/mach-ep93xx/mm.c.*/
#defineIO_BASE_VIRT0xFF000000//VirtualaddressofIO
#defineIO_BASE_PHYS0x80000000//PhysicaladdressofIO
#defineIO_SIZE0x00A00000//Howmuch?
完成映射的函數(shù)是ep93xx_map_io,所有要進(jìn)行映射內(nèi)存都在ep93xx_io_desc結(jié)構(gòu)當(dāng)中描述,我們的串口映射也加在這個(gè)地方,基址分別如下:
文件:linux-2.4.21/include/asm-arm/arch-ep93xx/regmap.h
#defineIO_W83697_UART_BASE0x20000000
#defineIO_W83697_UART_SIZE0x1000
#defineIO_W83977_UART_BASE0x30000000
#defineIO_W83977_UART_SIZE0x1000
#defineIO_SIZE_2(IO_SIZE+0x100000)
#defineIO_BASE83697_VIRTIO_BASE_VIRT+IO_SIZE
#defineIO_BASE83977_VIRTIO_BASE_VIRT+IO_SIZE_2
ep93xx_map_io完成是在arch初始化中賦值給structmachine_descmdesc這個(gè)機(jī)器描述結(jié)構(gòu)體,主要由位于mach-ep93xxarch.c文件中如下宏完成此結(jié)構(gòu)的初始化:
MACHINE_START(EDB9302,"edb9302")
…..
MAPIO(ep93xx_map_io)//初始化.map_io=ep93xx_map_io….
MACHINE_END
終這個(gè)函數(shù)在調(diào)用路徑如下:
start_kernel→setup_arch→paging_init→(mdesc-map_io())
至此完成串口物理內(nèi)存的映射,這個(gè)過程在console_init調(diào)用之前,因此不會(huì)有問題,此種方法建立映射是直接創(chuàng)建物理內(nèi)存頁與虛存頁的對(duì)應(yīng),大小為4k一頁,終調(diào)用的是create_mapping(),建立頁表映射是與具體的平臺(tái)相關(guān)的,位于mach_ep93xx/mm/proc-arm920.S文件中提供了與具體平臺(tái)相關(guān)的頁表建立函數(shù),其中包括TLB表操作/Cache操作/頁表操作等:
在上層的start_kernel→setup_arch→setup_processor調(diào)用下,會(huì)在proc-arm920.S文件中查找"."節(jié)的__arm920_proc_info,并從中找到配置的process相關(guān)的操作函數(shù),具體的arm頁表建立的詳情須要參看ARM內(nèi)存管理的相關(guān)手冊(cè).
.section".",#alloc,#execinstr
.type__arm920_proc_info,#object
__arm920_proc_info:
.long0x41009200
……
.longarm920_processor_functions
.size__arm920_proc_info,.-__arm920_proc_info
在arm920_processor_functions中包含的頁表操作如下:
/*pgtable*/
.wordcpu_arm920_set_pgd
.wordcpu_arm920_set_pmd
.wordcpu_arm920_set_pte
2.與串口硬件相關(guān)的宏主.
如下,下面將詳術(shù)如下,并指出其具體被使用的環(huán)境上下文:
1.讀寫數(shù)據(jù).
#defineUART_GET_CHAR(p)((readb((p)-membase+W83697_UARTDR))0xff)
#defineUART_PUT_CHAR(p,c)writeb((c),(p)-membase+W83697_UARTDR)
2.接收發(fā)送狀態(tài).
#defineUART_GET_RSR(p)((readb((p)-membase+W83697_UARTRSR))0xff)
#defineUART_PUT_RSR(p,c)writeb((c),(p)-membase+W83697_UARTRSR)
3.發(fā)送及接收中斷狀態(tài).
#defineUART_GET_CR(p)((readb((p)-membase+W83697_UARTCR))0xff)
#defineUART_PUT_CR(p,c)writeb((c),(p)-membase+W83697_UARTCR)
#defineUART_GET_INT_STATUS(p)((readb((p)-membase+W83697_UARTIIR))0xff)
4.以及其它的中斷使能設(shè)置等,在傳送時(shí)打開傳送中斷即會(huì)產(chǎn)生傳送中斷.
#defineUART_PUT_ICR(p,c)writeb((c),(p)-membase+W83697_UARTICR)
5.FIFO的狀態(tài),是否讀空/是否寫滿;每次讀時(shí)必須讀至FIFO空,寫時(shí)必須等到FIFO不滿時(shí)才能寫(要等硬件傳送完).
接收中斷讀空FIFO的判斷:
status=UART_GET_FR(port);
while(UART_RX_DATA(status)max_count--){
……
}
發(fā)送中斷寫FIFO:當(dāng)發(fā)送緩沖區(qū)中有數(shù)據(jù)要傳送時(shí),置發(fā)送中斷使能,隨后即產(chǎn)生傳送中斷,此時(shí)FIFO為空,傳送半個(gè)FIFO大小的字節(jié),如果發(fā)送緩沖區(qū)數(shù)據(jù)傳完,則關(guān)閉發(fā)送中斷.
6.傳送時(shí)可直接寫串口數(shù)據(jù)口,而不使用中斷,但必須等待檢測(cè)FIFO的狀態(tài)
do{
status=UART_GET_FR(port);
}while(!UART_TX_READY(status));//waitfortxbuffernotfull...
3.串口驅(qū)動(dòng)的參數(shù)配置
串口的參數(shù)主要包括如下幾個(gè)參數(shù),全部都記錄在uart_port結(jié)構(gòu)上,為靜態(tài)的賦值,本串口驅(qū)動(dòng)支持6個(gè)設(shè)備,所以驅(qū)動(dòng)中就包括了6個(gè)port,一個(gè)串口對(duì)應(yīng)一個(gè)port口,他們之間除了對(duì)應(yīng)的中斷號(hào)/寄存器起始基址/次設(shè)備號(hào)不同之外,其它的參數(shù)基本相同.
√串口對(duì)應(yīng)中斷,這里六個(gè)串口,其中有3個(gè)串口使用的系統(tǒng)外部中斷0/1/2,其中另外幾個(gè)中斷用提GPIO中斷,具體有關(guān)GPIO中斷的內(nèi)容可參見EP93XX芯片手冊(cè),GPIO中斷共享一個(gè)系統(tǒng)中斷向量,涉及中斷共享的問題,后面將詳述LINUX中的中斷共享支持.
√串口時(shí)鐘,串口時(shí)鐘用來轉(zhuǎn)換計(jì)算須要設(shè)置到配置寄存器當(dāng)中的波特率比值,其計(jì)算方法為:quot=(port-uartclk/(16*baud));baud為當(dāng)前設(shè)置的波特率,可為115200等值,取決于所選的串口時(shí)鐘源,quot即為要設(shè)置到寄存器當(dāng)中的比值.
√串口基址,即串口所有配置寄存器基礎(chǔ)址.
√串口次設(shè)備號(hào)(由驅(qū)動(dòng)中的次設(shè)備號(hào)依次累加)
前面已經(jīng)講過了六個(gè)串口中斷,這里詳細(xì)列出對(duì)應(yīng)情況如下,方便查找:
w83697的三個(gè)串口對(duì)應(yīng)中斷如下:
uart1:讀寫數(shù)據(jù)寄存器偏移為00x3F8,對(duì)應(yīng)系統(tǒng)外部中斷INT_EXT[0].
uart2:讀寫數(shù)據(jù)寄存器偏移為00x2F8,對(duì)應(yīng)系統(tǒng)外部中斷INT_EXT[1].
uart3:讀寫數(shù)據(jù)寄存器偏移為00x3e8,對(duì)應(yīng)系統(tǒng)外部中斷INT_EXT[2].
uart4:讀寫數(shù)據(jù)寄存器偏移為00x3e8,對(duì)應(yīng)EGPIO[8].
w83977的兩個(gè)串口對(duì)應(yīng)中斷如下:
uart1:讀寫數(shù)據(jù)寄存器偏移為00x3F8,對(duì)應(yīng)EGPIO[1].
uart2:讀寫數(shù)據(jù)寄存器偏移為00x2F8,對(duì)應(yīng)EGPIO[2].
下面列出其中一個(gè)具體的串口port的定義如下:
{
.port={
.membase=(void*)W83697_UART4_BASE,
.mapbase=W83697_UART4_BASE,
.iotype=SERIAL_IO_MEM,
.irq=W83697_IRQ_UART4,//串口中斷號(hào)
.uartclk=1846100,//uart時(shí)鐘,默認(rèn).
.fifosize=8,//硬件fifo大小.
.ops=amba_pops,//底層驅(qū)動(dòng)的硬件操作集,如開關(guān)中斷等.
.flags=ASYNC_BOOT_AUTOCONF,
.line=3,//串口在次設(shè)備數(shù)組中的索引號(hào),須注意從0計(jì)起…
},
.dtr_mask=0,
.rts_mask=0,
}
4.串口驅(qū)動(dòng)的底層接口函數(shù)
驅(qū)動(dòng)文件:linux-2.4.21/drivers/serial/Ep93xx_w83697.c
相關(guān)文件:linux-2.4.21/drivers/serial/core.c下面詳述.
函數(shù):w83697uart_rx_chars(structuart_port*port,structpt_regs*regs)
描述:串口接收數(shù)據(jù)中斷,此函數(shù)中應(yīng)當(dāng)注意的要點(diǎn)如下:
接收數(shù)據(jù)時(shí),要注意判斷FIFO是否讀空(參見上述2點(diǎn)中說明).
接收數(shù)據(jù)放入flip緩沖區(qū),此緩沖區(qū)專供緩存中斷中接收到的數(shù)據(jù),是原始的串口數(shù)據(jù),為更上一層中各種終端處理模式的原始數(shù)據(jù),可以進(jìn)行各種加工處理。
接收數(shù)據(jù)到flip緩沖區(qū)中時(shí),須根據(jù)硬件接收狀態(tài),置每一個(gè)接收到的字符的接收標(biāo)志,放在flag_buf_ptr當(dāng)中,標(biāo)志類型有TTY_NORMAL/TTY_PARITY/TTY_FRAME等,分別表示正常/校驗(yàn)出錯(cuò)/幀出錯(cuò)(無停止位)等.
每接收數(shù)據(jù)之后,會(huì)通過在退出中斷前調(diào)用tty_flip_buffer_push()來往tq_timer任務(wù)列表中加一個(gè)隊(duì)列任務(wù),串口的隊(duì)列任務(wù)主要是負(fù)責(zé)將中斷接收到flip緩沖區(qū)中的數(shù)據(jù)往上傳輸至終端終沖區(qū),隊(duì)列任務(wù)的機(jī)制將在后面介紹,它是一種異步執(zhí)行機(jī)制,在軟中斷中觸發(fā)執(zhí)行.
函數(shù):staticvoidw83697uart_tx_chars(structuart_port*port)
描述:串口發(fā)送數(shù)據(jù)中斷,發(fā)送中斷中要做的事比較少,比起接收中斷簡(jiǎn)單了好多,注意事項(xiàng)如下:
當(dāng)上層要發(fā)送數(shù)據(jù)時(shí),就會(huì)打開串口發(fā)送中斷,此時(shí)FIFO為空,傳送半個(gè)FIFO大小數(shù)據(jù)即退出,通常打開中斷是通過更上一層的uart_flush_chars()調(diào)用,終調(diào)用的是w83697uart_start_tx().
檢測(cè)當(dāng)沒有數(shù)據(jù)要傳輸?shù)臅r(shí)候,關(guān)閉傳送中斷,在傳送之前與傳送完之后都有檢測(cè).
重要的一點(diǎn)是如果傳送緩沖區(qū)當(dāng)中的字符數(shù)已經(jīng)小于WAKEUP_CHARS,則可以喚醒當(dāng)前正在使用串口進(jìn)行傳送的進(jìn)程,這里是通過tasklet機(jī)制來完成,這也是一異步執(zhí)行機(jī)制.
順帶介紹開關(guān)中斷接口:
staticvoidw83697uart_start_tx(structuart_port*port,unsignedinttty_start)
staticvoidw83697uart_stop_tx(structuart_port*port,unsignedinttty_stop)
函數(shù):staticvoidw83697uart_int(intirq,void*dev_id,structpt_regs*regs)
描述:中斷處理函數(shù),為3個(gè)使用系統(tǒng)外部中斷的的串口的中斷入口,其中必須處理的中斷狀態(tài)分為如下幾種,注意必須在處理中斷時(shí)根據(jù)手冊(cè)中的說明來清除中斷,通常是讀或?qū)懩承┘拇嫫骷纯伞?/p>
接收中斷.
傳送中斷.
FIFO超時(shí)中斷.
其它不具體處理的中斷,必須讀相應(yīng)寄存器清中斷.
函數(shù):staticvoidw83697uart_int2(intirq,void*dev_id,structpt_regs*regs)
描述:中斷處理函數(shù),為另外幾個(gè)使用串口使用的GPIO中斷入口,GPIO中斷共享同一個(gè)系統(tǒng)中斷向量,必須根據(jù)GPIO的中斷狀態(tài)寄存器的相應(yīng)位來判斷對(duì)應(yīng)的中斷是屬哪一個(gè)串口的,從而進(jìn)行相應(yīng)的處理,其實(shí)這個(gè)判斷也是無所謂的,因?yàn)橹袛喈a(chǎn)生時(shí)傳進(jìn)來的參數(shù)已經(jīng)含有了相應(yīng)串口的參數(shù),在判斷完中斷產(chǎn)生的GPIO口后立即調(diào)用w83697uart_int2完成具體的中斷處理.
函數(shù):staticintw83697uart_startup(structuart_port*port)
描述:串口開啟后的初始化函數(shù),主要完成初始化配置,以及安裝中斷處理了函數(shù),初始化配置包括打開中斷使能標(biāo)志。
函數(shù):staticvoidw83697uart_shutdown(structuart_port*port)
描述:串口關(guān)閉函數(shù),清除配置,半閉中斷.
函數(shù):staticvoidw83697uart_change_speed(structuart_port*port,unsignedintcflag,unsignedintiflag,unsignedintquot)
描述:配置函數(shù),經(jīng)由上次調(diào)用下來,主要配制串口的波特率比,以及各種容錯(cuò)處理,在串口打開初始化時(shí)會(huì)被調(diào)用,在必變串口波特率/校驗(yàn)方式/停止位/傳送位數(shù)等參數(shù)時(shí)會(huì)被調(diào)用.
5.串口驅(qū)動(dòng)與上層的接口關(guān)聯(lián)
文件:linux-2.4.21/drivers/serial/core.c
這一層接口是串口驅(qū)動(dòng)中的共用部分代碼,結(jié)構(gòu)為structuart_driver.這一層上承TTY終端,下啟串口底層,串口底層主要處理了與串口硬件相關(guān)的部分,并向上提供uart中間層向下的接口.Uartcoar向下與底層驅(qū)動(dòng)的接口,通過一個(gè)staticstructuart_opsamba_pops結(jié)構(gòu)完成?這個(gè)結(jié)構(gòu)直接賦值給串口structuart_amba_portamba_ports的.ops成員,將串口的port加入到uart_driver當(dāng)中完成關(guān)聯(lián),通過uart_add_one_port加入.
staticint__initw83697uart_init(void)
{
intret,i;
ret=uart_register_driver(amba_reg);
if(ret==0){
for(i=0;iUART_NR;i++)
uart_add_one_port(amba_reg,amba_ports[i].port);
}
returnret;
}
二.Linux的中斷機(jī)制及中斷共享機(jī)制.
前面講到了有6個(gè)串口,除了w83697中的前三個(gè)串使用的是獨(dú)立的系統(tǒng)外部中斷之外,其它的在個(gè)串口是共享一個(gè)系統(tǒng)中斷向量的,現(xiàn)在我們來看看多個(gè)中斷是如何掛在一個(gè)系統(tǒng)中斷向量表當(dāng)中的,共享中斷到底是什么樣的一種機(jī)制?
進(jìn)行分析代碼可知,linux下的中斷采用的是中斷向量的方式,每一個(gè)中斷對(duì)應(yīng)一個(gè)中斷描述數(shù)組當(dāng)中的一項(xiàng),結(jié)構(gòu)為structirqdesc,其當(dāng)中對(duì)應(yīng)一成員結(jié)構(gòu)為structirqactionr的成員action,這個(gè)即表示此中斷向量對(duì)應(yīng)的中斷處理動(dòng)作,這里引用從網(wǎng)上的一幅圖講明中斷向量表與中斷動(dòng)作之間的關(guān)系:
structirqaction{
void(*handler)(int,void*,structpt_regs*);
unsignedlongflags;
unsignedlongmask;
constchar*name;
void*dev_id;
structirqaction*next;
};
從上面的結(jié)構(gòu)體與圖當(dāng)中,我們就可以很清楚的看到,一個(gè)中斷向量表可以對(duì)應(yīng)一個(gè)irqaction,也可能對(duì)應(yīng)多個(gè)由鏈表鏈在一起的一個(gè)鏈表irqaction,這當(dāng)中主要在安裝中斷的時(shí)候通過中斷的標(biāo)志位來決定:
安裝中斷處理,不可共享:
retval=request_irq(port-irq,w83697uart_int,0,"w83697_uart3",port);
安裝中斷處理,可共享:
retval=request_irq(port-irq,w83697uart_int2,SA_SHIRQ,"w83977_uart5",port);
由上即可知,安裝共享中斷時(shí),只須指定安裝的中斷標(biāo)志位flag為SA_SHIRQ,進(jìn)入分析安裝中斷的處理可知,在安裝時(shí),會(huì)檢測(cè)已經(jīng)安裝的中斷是否支持共享中斷,如果不支持,則新的中斷安裝動(dòng)作失?。蝗绻呀?jīng)安裝的中斷支持共享中斷,則還必須檢測(cè)將要安裝的新中斷是否支持中斷共享,如果不支持則安裝還是會(huì)失敗,如果支持則將此新的中斷處理鏈接到此中斷向量對(duì)應(yīng)的中斷動(dòng)作處理鏈表當(dāng)中.
在產(chǎn)生中斷時(shí),共享中斷向量中對(duì)應(yīng)的中斷處理程序鏈表中的每一個(gè)都會(huì)被調(diào)用,依據(jù)鏈表的次序來,這樣處理雖然會(huì)有影響到效率,但是一般情況下中斷傳到用戶的中斷處理服務(wù)程序中時(shí),由用戶根據(jù)硬件的狀態(tài)來決定是否處理中斷,所以能常情況下都是立即就返回了,效率的影響不會(huì)是大的問題.
三.Linux的軟中斷機(jī)制.
前面已經(jīng)簡(jiǎn)單講過了LINUX下的硬中斷處理機(jī)制,其實(shí)硬中斷的處理都由LINUX底層代碼具體完成了,使用者一般在處理硬中斷時(shí)是相當(dāng)簡(jiǎn)單的,只須要用request_irq()簡(jiǎn)單的掛上中斷即可,這里我們進(jìn)一步介紹一下LINUX下的軟中斷機(jī)制,軟中斷機(jī)制相比起硬中斷機(jī)制稍微復(fù)雜一些,而且在LINUX內(nèi)核本身應(yīng)用非常的廣,它作為一種軟性的異步執(zhí)行機(jī)制,只有深入理解了它才能靈活的運(yùn)用.
之所以提到內(nèi)核的softirq機(jī)制,主要是因?yàn)樵诖谥袛嘁彩褂昧诉@些機(jī)制,理解了這些機(jī)制就能更加明白串口驅(qū)動(dòng)一些問題,現(xiàn)在先提出幾個(gè)問題如下:
前面提供到中斷接收后數(shù)據(jù),先放到flip緩沖區(qū)當(dāng)中,這樣讓人很容易進(jìn)一步想知道,中斷處理的緩沖區(qū)的數(shù)據(jù),用戶進(jìn)程讀取串口時(shí)如何讀到的?很明顯中斷處于內(nèi)核空間,用戶讀取串口輸入進(jìn)程是在用戶空間,中斷緩沖區(qū)中的數(shù)據(jù)如何被處理到終端緩沖區(qū)中,供用戶讀取的?
另外寫串口時(shí),是向終端緩沖區(qū)當(dāng)中寫入,那么上層的寫操作如何知道下層緩沖區(qū)中的的數(shù)據(jù)是否傳送完成?用戶空間的寫串口進(jìn)程處于什么樣的狀態(tài)?如果是寫完緩沖區(qū)就睡眠以保證高效的CPU使用率,那么何時(shí)才應(yīng)該醒過來?由誰負(fù)責(zé)醒過來?
1.往tq_timer任務(wù)隊(duì)列中添加一項(xiàng)任務(wù).
根據(jù)以上這兩個(gè)問題,我們來深入代碼分析,首先看接收緩沖區(qū)中的數(shù)據(jù)如何上傳,前面已經(jīng)提到過,接收中斷處理完成后,會(huì)調(diào)用tty_flip_buffer_push(),這個(gè)函數(shù)完成的功能就是往一系統(tǒng)定義的任務(wù)隊(duì)列當(dāng)中加入一個(gè)任務(wù),下面我們將詳細(xì)的分析加入的任務(wù)終是如何執(zhí)行起來的.[任務(wù):這里所講的任務(wù)可以直接理解成為一個(gè)相應(yīng)的回調(diào)函數(shù),LINUX下術(shù)語稱作tasklet]
voidtty_flip_buffer_push(structtty_struct*tty)
{
if(tty-low_latency)
flush_to_ldisc((void*)tty);
else
queue_task(tty-flip.tqueue,tq_timer);
}
2.tq_timer的執(zhí)行路徑分析.
tq_timer是一個(gè)雙鏈表結(jié)構(gòu)任務(wù)隊(duì)列,每項(xiàng)任務(wù)包含一個(gè)函數(shù)指針成員,它通過run_task_queue每次將當(dāng)中的所有任務(wù)(其實(shí)是一些函數(shù)指針)全部調(diào)用,然后清空隊(duì)列,終的執(zhí)行tq_timer的是在中斷底半的tqueue_bh中執(zhí)行,如下:
voidtqueue_bh(void)
{
run_task_queue(tq_timer);
}
在void__initsched_init(void)當(dāng)中初始化底半的向量如,tqueue_bh初始化在bh_base的TIMER_BH位置,bh_base為一結(jié)構(gòu)很簡(jiǎn)單的數(shù)組,在什么位置調(diào)用什么樣的了函數(shù)基本已經(jīng)形成默認(rèn)的習(xí)慣:
init_bh(TIMER_BH,timer_bh);
init_bh(TQUEUE_BH,tqueue_bh);
init_bh(IMMEDIATE_BH,immediate_bh);
看看init_bh相當(dāng)于初始底半的服務(wù)程序,非常簡(jiǎn)單:
voidinit_bh(intnr,void(*routine)(void))
{
bh_base[nr]=routine;
mb();
}
終真正的執(zhí)行bh_base中保存的函數(shù)指針的,在bh_action()當(dāng)中:
staticvoidbh_action(unsignedlongnr)
{
…
if(bh_base[nr])
bh_base[nr]();
…
}
關(guān)于這里所指出的bh_base,我們?cè)诤竺婢椭苯臃Q作bh,意即中斷底半所做的事.
3.tq_timer實(shí)現(xiàn)所依賴的tasklet.
那么bh_action在什么時(shí)候執(zhí)行呢?bh_action被初始化成bh_task_vec這32個(gè)tasklet調(diào)用的任務(wù),因此它的依賴機(jī)制是tasklet機(jī)制,后面將進(jìn)行簡(jiǎn)單介紹.
void__initsoftirq_init()
{
inti;
for(i=0;i32;i++)
tasklet_init(bh_task_vec+i,bh_action,i);
….
}
至此已經(jīng)把任務(wù)隊(duì)列的執(zhí)行流程及原理分析完成,tasklet是須要激活的,這里我們先指出任務(wù)隊(duì)列是如何激活的,在時(shí)鐘中斷的do_timer()當(dāng)中會(huì)調(diào)用mark_bh(TIMER_BH),來激時(shí)鐘底半所依賴運(yùn)行的tasklet,其中bh_task_vec的所有成員的函數(shù)指針全部指向bh_action.
staticinlinevoidmark_bh(intnr)
{
tasklet_hi_schedule(bh_task_vec+nr);
}
tasklet_hi_schedule的功能就是往tasklet當(dāng)中加入一個(gè)新的tasklet.
4.tasklet的機(jī)制簡(jiǎn)單分析.
講到tasklet,我們才與我們真正要講的softirq近了,因?yàn)槟壳霸谲浿袛喈?dāng)中有主要的應(yīng)用就是tasklet,而且在所有32個(gè)軟中斷中僅有限的幾個(gè)軟中斷如下:
enum{
HI_SOFTIRQ=0,
NET_TX_SOFTIRQ,
NET_RX_SOFTIRQ,
TASKLET_SOFTIRQ
};
structsoftirq_action{
void(*action)(structsoftirq_action*);
void*data;
};
staticstructsoftirq_actionsoftirq_vec[32]__cacheline_aligned;//軟中斷的中斷向量表,實(shí)為數(shù)組.
[1].初始化軟中斷向量.
我們這里所要講的,就是HI_SOFTIRQ/TASKLET_SOFTIRQ兩項(xiàng),據(jù)我理解這兩項(xiàng)根本在實(shí)現(xiàn)機(jī)制上一樣的,之所以分開兩個(gè)名字叫主要是為了將不同的功能分開,就類似于雖然同是軟中斷,但是各處所完成的功能不一樣,所以分在兩個(gè)軟中斷完成,后面我們僅取其中用于執(zhí)行時(shí)鐘底半的任務(wù)隊(duì)列HI_SOFTIRQ為例進(jìn)行講解,而且我們不講及多個(gè)CPU情況下的tasklet相關(guān)機(jī)制,這兩項(xiàng)軟中斷的實(shí)始化如下:
void__initsoftirq_init()
{
….
open_softirq(TASKLET_SOFTIRQ,tasklet_action,NULL);
open_softirq(HI_SOFTIRQ,tasklet_hi_action,NULL);
}
open_softirq下所做的事相當(dāng)簡(jiǎn)單,即往軟中斷向量中賦值,相當(dāng)于硬中斷當(dāng)中的request_irq掛硬件中斷:
voidopen_softirq(intnr,void(*action)(structsoftirq_action*),void*data)
{
softirq_vec[nr].data=data;
softirq_vec[nr].action=action;
}
[2].軟中斷中斷服務(wù)程序
對(duì)于HI_SOFTIRQ,相應(yīng)的中斷服務(wù)程序?yàn)閠asklet_hi_action,由上文所講的初始化過程給出,這個(gè)函數(shù)目前完成的功能相當(dāng)簡(jiǎn)單,它的任務(wù)就是遍歷執(zhí)行此中斷所對(duì)應(yīng)一個(gè)tasklet鏈表,
NR_CPUS=1.
structtasklet_headtasklet_hi_vec[NR_CPUS]__cacheline_aligned;
[3].往軟中斷對(duì)應(yīng)的tasklet鏈表中加入新的tasklet,加在尾部.
void__tasklet_hi_schedule(structtasklet_struct*t)
{
…
t-next=tasklet_hi_vec[cpu].list;
tasklet_hi_vec[cpu].list=t;
cpu_raise_softirq(cpu,HI_SOFTIRQ);
…
}
重要的一點(diǎn)是,在安裝了新的tasklet后,還必須將軟中斷設(shè)置為激活,告訴系統(tǒng)有軟中斷須要執(zhí)行了,下面一點(diǎn)即提到系統(tǒng)如何檢測(cè)是否有軟中斷須要處理:
#define__cpu_raise_softirq(cpu,nr)do{softirq_pending(cpu)|=1UL(nr);}while(0)
[4].軟中斷所依賴的執(zhí)行機(jī)制.
講到還沒有指出軟中斷是如何觸發(fā)執(zhí)行的,其實(shí)很簡(jiǎn)單:
在系統(tǒng)處理所有硬中斷信號(hào)時(shí),他們的入口是統(tǒng)一的,在這個(gè)入口函數(shù)當(dāng)中除了執(zhí)行do_IRQ()完成硬件中斷的處理之外,還會(huì)執(zhí)行do_softirq()來檢測(cè)是否有軟中斷須要執(zhí)行,所以軟中斷所依賴的是硬件中斷機(jī)制;
另外還有一個(gè)專門處理軟中斷內(nèi)核線程ksoftirqd(),這個(gè)線程處理軟中斷級(jí)別是比較低的,他是一個(gè)無限LOOP不停的檢測(cè)是否有軟中斷須要處理,如果沒有則進(jìn)行任務(wù)調(diào)度.
在do_softirq()中有如下的判斷,以決定是否有軟中斷須要執(zhí)行,如果沒有就直接退出,在[3]中提到的激活軟中斷時(shí),要將相應(yīng)軟中斷位置1,軟中斷有32個(gè),因此一個(gè)整型數(shù)即可以表示32個(gè)軟中斷,即可判斷有什么樣的軟中斷須要處理,代碼如下:
pending=softirq_pending(cpu);
if(pending){
}
….
do{//檢測(cè)32個(gè)軟中斷位標(biāo)志中是否有為1的…
if(pending1)
h-action(h);
h++;
pending=1;
}while(pending);
[4].軟中斷所依賴的執(zhí)行時(shí)期問題.
之所以將這個(gè)問題單獨(dú)列開來講,是因?yàn)樗貏e的重要,上面我已經(jīng)講過了軟中斷是依賴硬中斷觸發(fā)執(zhí)行的,但是產(chǎn)生如下疑問:
是不是一有硬中斷發(fā)生就會(huì)觸發(fā)軟中斷的執(zhí)行?
軟中斷的執(zhí)行會(huì)不會(huì)影響到系統(tǒng)的性能?
會(huì)不會(huì)影響到硬中斷的處理效率?也就是說會(huì)不會(huì)導(dǎo)致在處理軟中斷時(shí)而引起硬中斷無法及時(shí)響應(yīng)呢?
再看do_softirq的代碼當(dāng)中有如下判斷:
if(in_interrupt())
return;
這個(gè)條件就是能否進(jìn)行軟中斷處理的關(guān)鍵條件,因此由此也可以了解到軟中斷是一種優(yōu)先級(jí)低于硬中斷的軟性機(jī)制,具體來看看這個(gè)判斷條件是什么:
/*Areweinaninterruptcontext?Eitherdoingbottomhalf
*orhardwareinterruptprocessing?*/
#definein_interrupt()({constint__cpu=smp_processor_id();
(local_irq_count(__cpu)+local_bh_count(__cpu)!=0);})
/*softirq.hissensitivetotheoffsetsofthesefields*/
typedefstruct{
unsignedint__softirq_pending;
unsignedint__local_irq_count;
unsignedint__local_bh_count;
unsignedint__syscall_count;
structtask_struct*__ksoftirqd_task;/*waitqueueistoolarge*/
}____cacheline_alignedirq_cpustat_t;
#defineirq_enter(cpu,irq)(local_irq_count(cpu)++)
#defineirq_exit(cpu,irq)(local_irq_count(cpu)--)
看到這里,不得不再多注意一個(gè)結(jié)構(gòu),那就是irq_cpustat_t,先前我們講是否有軟中斷產(chǎn)生的標(biāo)志位,但沒有提到__softirq_pending,這個(gè)變量就是記載32個(gè)軟中斷是否產(chǎn)生的標(biāo)志,每一個(gè)軟中斷對(duì)應(yīng)一個(gè)位;在中斷執(zhí)行的do_softirq中有如下幾個(gè)重要的動(dòng)作,說明如下:
in_interrupt判斷是否可以進(jìn)行軟中斷處理,判斷的條件就是沒有沒處在硬件中斷環(huán)境中,而且還沒有軟中斷正在執(zhí)行(即不允許軟中斷嵌套),軟中斷的嵌套避免是通過local_bh_disable()/local_bh_enable()實(shí)現(xiàn),至于帶有bh,其意也即指softirq是中斷底半(bh),在處理硬件中斷時(shí),一進(jìn)行即會(huì)調(diào)用irq_enter來表示已經(jīng)進(jìn)入硬件中斷處理程序,處理完硬件中斷后再調(diào)用irq_exit表示已經(jīng)完成處理;
pending判斷是否有軟中斷須要處理,每個(gè)位用作當(dāng)作一個(gè)軟中斷是否產(chǎn)生的標(biāo)志.
清除所有軟中斷標(biāo)志位,因?yàn)橄旅婕磳⑻幚?但清除之前先緩存起來,因?yàn)橄旅孢€要使用這個(gè)變量.
在進(jìn)入軟中斷處理后,會(huì)關(guān)閉bh功能的執(zhí)行,執(zhí)行完后才打開,這樣在in_interrupt判斷當(dāng)中就會(huì)直接發(fā)現(xiàn)已經(jīng)有bh在執(zhí)行,不會(huì)再次進(jìn)入bh執(zhí)行了,這嚴(yán)格保證了bh執(zhí)行的串行化.
打開硬件中斷,讓軟中斷在有硬件中斷的環(huán)境下執(zhí)行.
處理完軟中斷后關(guān)閉硬中斷,再次檢測(cè)是否有新的軟中斷產(chǎn)生,如果有的話,卻只須立即處理本次軟中斷過程未發(fā)生過的軟中斷向量.之所以會(huì)有新的軟中斷產(chǎn)生,那是因?yàn)檐浿袛嗍窃陂_硬件中斷的情況下執(zhí)行,硬件中斷處理是可能又產(chǎn)生了新的軟中斷.之所以只處理本次軟中斷未發(fā)生的軟中斷向量,依據(jù)我自己的理解,其目的是為了不加重軟中斷處理的負(fù)擔(dān)而不馬上處理,只是相應(yīng)的喚醒一個(gè)wakeup_softirqd線程,這是專門處理軟中斷的,這樣雖然延誤了軟中斷的處理,但避免了在硬中斷服務(wù)程序中拖延太長(zhǎng)的時(shí)間.[關(guān)于軟中斷的處理在后緒版本變化也很大,可以進(jìn)一步學(xué)習(xí)研究,如何使軟中斷不至影響中斷處理效率]
軟中斷處理這個(gè)函數(shù)雖然不長(zhǎng),但是相當(dāng)?shù)年P(guān)鍵,每一句代碼都很重要,結(jié)合上面所說的幾點(diǎn),與源碼交互起來理解才能根本理解軟中斷的設(shè)計(jì)機(jī)制:
asmlinkagevoiddo_softirq()
{
intcpu=smp_processor_id();
__u32pending;
unsignedlongflags;
__u32mask;
if(in_interrupt())return;
local_irq_save(flags);
pending=softirq_pending(cpu);
if(pending){
structsoftirq_action*h;
mask=~pending;
local_bh_disable();
restart:
/*Resetthependingbitmaskbeforeenablingirqs*/
softirq_pending(cpu)=0;
local_irq_enable();
溫馨提示
- 1. 本站所有資源如無特殊說明,都需要本地電腦安裝OFFICE2007和PDF閱讀器。圖紙軟件為CAD,CAXA,PROE,UG,SolidWorks等.壓縮文件請(qǐng)下載最新的WinRAR軟件解壓。
- 2. 本站的文檔不包含任何第三方提供的附件圖紙等,如果需要附件,請(qǐng)聯(lián)系上傳者。文件的所有權(quán)益歸上傳用戶所有。
- 3. 本站RAR壓縮包中若帶圖紙,網(wǎng)頁內(nèi)容里面會(huì)有圖紙預(yù)覽,若沒有圖紙預(yù)覽就沒有圖紙。
- 4. 未經(jīng)權(quán)益所有人同意不得將文件中的內(nèi)容挪作商業(yè)或盈利用途。
- 5. 人人文庫網(wǎng)僅提供信息存儲(chǔ)空間,僅對(duì)用戶上傳內(nèi)容的表現(xiàn)方式做保護(hù)處理,對(duì)用戶上傳分享的文檔內(nèi)容本身不做任何修改或編輯,并不能對(duì)任何下載內(nèi)容負(fù)責(zé)。
- 6. 下載文件中如有侵權(quán)或不適當(dāng)內(nèi)容,請(qǐng)與我們聯(lián)系,我們立即糾正。
- 7. 本站不保證下載資源的準(zhǔn)確性、安全性和完整性, 同時(shí)也不承擔(dān)用戶因使用這些下載資源對(duì)自己和他人造成任何形式的傷害或損失。
最新文檔
- 鋼筋混凝土防腐蝕技術(shù)方案
- 土方開挖成品保護(hù)方案
- 婦幼保健院藥品存儲(chǔ)改造方案
- 消防設(shè)備現(xiàn)場(chǎng)安裝指導(dǎo)方案
- 消防泵房噪聲控制方案
- 2026年食品衛(wèi)生與安全知識(shí)競(jìng)賽試題庫及答案
- 壓實(shí)設(shè)備選擇與使用方案
- 消防設(shè)施環(huán)境友好型材料應(yīng)用方案
- 土方開挖施工安全防護(hù)方案
- 2026年醫(yī)療廢棄物處理筆試醫(yī)療廢物報(bào)廢規(guī)定與環(huán)保措施試題
- 2026年1月浙江省高考(首考)地理試題(含答案)
- 職高信息技術(shù)題目及答案
- 2026年各地高三語文1月聯(lián)考文言文匯編(文言詳解+挖空)
- 冰箱安裝施工方案
- 急性失代償性心力衰竭管理的研究進(jìn)展2026
- 老年人摔傷后的長(zhǎng)期護(hù)理計(jì)劃
- 2026年黑龍江民族職業(yè)學(xué)院?jiǎn)握新殬I(yè)傾向性考試題庫帶答案詳解
- 2026年春節(jié)安全培訓(xùn):平安過節(jié)安全相伴
- 消防維保應(yīng)急預(yù)案及措施
- 2026元旦主題班會(huì):馬年猜猜樂猜成語 (共130題)【課件】
- 2026年盤錦職業(yè)技術(shù)學(xué)院?jiǎn)握新殬I(yè)技能測(cè)試題庫及參考答案詳解一套
評(píng)論
0/150
提交評(píng)論