十分钟黑屏的问题
轉(zhuǎn)自:http://blog.csdn.net/lanmanck/article/details/5148917
又是好久沒(méi)有更新了,同樣記錄下這段時(shí)間干的事情。? 來(lái)到公司報(bào)道之后我就去了工廠,呆了一個(gè)多月,熟悉公司各種產(chǎn)品的生產(chǎn)過(guò)程。當(dāng)然,就是和生產(chǎn)線上的小MM們聊聊天,哈哈,開(kāi)玩笑了。這期間我 的導(dǎo)師給我了個(gè)任務(wù)——一個(gè)UI的低成本實(shí)現(xiàn)。這個(gè)UI必須具有LCD顯示、USB主機(jī)、網(wǎng)絡(luò)等功能,要求就是低成本低成本再低成本;很自然選型選完之后 就又變成搞ARM了。買了開(kāi)發(fā)板,硬件就沒(méi)有什么好說(shuō)的了。任務(wù)的重點(diǎn)落在了軟件上,剛開(kāi)始我和導(dǎo)師達(dá)成的意見(jiàn)是代碼裸奔(省去授權(quán)費(fèi)用),或者穿個(gè)自己 寫的小OS。于是我花了2個(gè)星期的時(shí)間寫了0.8(寫了任務(wù)切換,其他沒(méi)有最后寫完)個(gè)類似uCOS功能的小OS,發(fā)現(xiàn)這種東西跑在ARM920T上實(shí)在 發(fā)揮不出920T的功能,于是我又花了2個(gè)星期寫了存儲(chǔ)管理,打算加入MMU;但是寫完發(fā)現(xiàn)就算系統(tǒng)能工作,我怎么調(diào)試應(yīng)用程序是個(gè)大問(wèn)題,因?yàn)橛昧?MMU,用戶進(jìn)程就不能和內(nèi)核一起玩了。經(jīng)過(guò)和導(dǎo)師的商量,我再次回到了Linux;目前的打算是使用免費(fèi)的內(nèi)核,自己寫個(gè)類似TC的圖形庫(kù),或者使用 Microwindows + FLTK之類的不要錢的東西。很浪費(fèi)時(shí)間啊,花了4個(gè)星期才發(fā)現(xiàn)原來(lái)自己寫系統(tǒng)真的是非常困難的。但是這4個(gè)星期也沒(méi)有白花,有些附屬產(chǎn)品,例如自己寫的 不需要stdlib庫(kù)的malloc。其實(shí)我是第二次寫這個(gè)東西了,第一次的不太成熟,用了幾次就發(fā)現(xiàn)局限性了。現(xiàn)在寫的這個(gè)是我啃了幾天操作系統(tǒng)原理寫 的,應(yīng)該相對(duì)好用,以后有空我會(huì)貼出代碼來(lái)。 然后就是我做過(guò)的Linux開(kāi)發(fā)過(guò)程了,其實(shí)說(shuō)起來(lái),這次開(kāi)發(fā)我發(fā)現(xiàn)自己Linux真的什么都不懂。過(guò)去偷懶沒(méi)有花時(shí)間好好看看Linux內(nèi)核,現(xiàn)在造成很多麻煩,今天這些問(wèn)題,熟悉內(nèi)核的人只需要1分鐘就能搞定了,我花了1天多。寫下這個(gè)筆記,以便以后查閱使用。 第一個(gè)問(wèn)題:啟動(dòng)Linux的時(shí)候LCD會(huì)全屏花屏大約0.5秒,然后左上角出現(xiàn)一塊不明花斑。 這個(gè)問(wèn)題相對(duì)簡(jiǎn)單。因?yàn)槲以贐ootloader里面打開(kāi)了液晶顯示,緩沖區(qū)映射在某個(gè)地址上,當(dāng)內(nèi)核初始化MMU的時(shí)候,LCD控制寄存器里 緩沖區(qū)的位置信息就不對(duì)了,或者是Bootloader使用的緩沖區(qū)被內(nèi)核的數(shù)據(jù)或代碼覆蓋,導(dǎo)致在內(nèi)核初始化LCD之前,LCD花屏。 那個(gè)不明花斑其實(shí)是Linux的可愛(ài)小企鵝圖片,但是可能因?yàn)榫彌_區(qū)像素位寬和格式、LCD調(diào)色板設(shè)置等問(wèn)題顯示不出來(lái)。 花屏解決方法:在Bootloader加載系統(tǒng)之前關(guān)掉LCD控制器或者關(guān)掉LCD的背光,這樣做比較簡(jiǎn)單。復(fù)雜的,就得改MMU映射部分代 碼,在修改了MMU映射之后,立即修改LCD緩沖的位置。反正我就關(guān)掉LCD控制器了,因?yàn)閮?nèi)核很快就會(huì)初始化LCD,Windows啟動(dòng)都黑屏呢,我們 黑那么2秒鐘也沒(méi)有什么大不了的。 花斑解決方法:相信如果做產(chǎn)品的話,不需要顯示什么企鵝給用戶看,所以可以在內(nèi)核選項(xiàng)里將這個(gè)企鵝logo關(guān)掉。具體位置在Device Drivers>Graphics support>Logo configuration,對(duì)應(yīng)的宏相信在.config里面很容易找到。如果非要顯示這個(gè)企鵝,那么可以在/drivers/video /console/fbcon.c里面找找,我也不知道怎么弄。 第二個(gè)問(wèn)題:Linux啟動(dòng)之后,只要一段時(shí)間不動(dòng)鍵盤(開(kāi)發(fā)板上用IO擴(kuò)展出來(lái)的鍵盤),LCD就會(huì)自動(dòng)關(guān)閉(黑屏、顯示慢慢消失之類),只要按下鍵盤就能恢復(fù)。 這個(gè)問(wèn)題讓我花了一天多的時(shí)間。其實(shí)如果是手持設(shè)備,這樣也沒(méi)有什么。但是我們公司的產(chǎn)品是要一直顯示東西的,必須解決這個(gè)問(wèn)題。我看了很多論 壇,有不少人也遇到了這個(gè)問(wèn)題,但是我剛才是搜索的時(shí)候,關(guān)鍵詞不對(duì),總找不到正確的答案。如果你遇到了同樣的問(wèn)題,而且不想看我的三腳貓分析,那么就在 百度上搜索“blankinterval”、“setterm -blank 0”之類的,馬上你就能找到簡(jiǎn)單的解決方案。 這個(gè)問(wèn)題很容易讓人想到屏幕保護(hù)和電源管理。的確,這是一種電源管理。但是,你卻無(wú)法從Linux內(nèi)核選項(xiàng)的電源管理中解決這個(gè)問(wèn)題。我們一步步來(lái)。 首先,我測(cè)量到LCD的PCLK時(shí)鐘消失了,這意味著內(nèi)核把LCD控制器關(guān)掉了。于是,從LCD驅(qū)動(dòng)程序著手。我用的是S3C2440,這是 2410的升級(jí)版,但是LCD控制器是一樣的,在我拿到的開(kāi)發(fā)板廠商給我做好驅(qū)動(dòng)的內(nèi)核里,驅(qū)動(dòng)的位置在/drivers/video /s3c2410fb.c。為什么后面有個(gè)fb呢?這是Framebuffer的縮寫,百度下你能找到很多關(guān)于它的解釋。Framebuffer是所有 Linux下GUI程序?qū)τ布僮鞯脑O(shè)備接口,位于/dev中,一般為fb0。在s3c2410fb.c中可以找到一個(gè)類似 s3c2410_disable_controller()這樣名稱的函數(shù),我的驅(qū)動(dòng)里叫pxafb_disable_controller(),可以看 出這個(gè)驅(qū)動(dòng)是從pxa處理器改的,當(dāng)然廠家不一樣名字也叫得不一樣。里面有一句類似這樣寫的 __raw_writel(fbi->reg.lcdcon1 & ~S3C2410_LCDCON1_ENVID, S3C2410_LCDCON1);,把這句話刪掉LCD就不會(huì)關(guān)掉了。這是第一個(gè)層次,我也看到有人是這樣做的。但是,這有問(wèn)題,按鍵盤恢復(fù)后,原本顯 示在屏幕上的東西如果你不重畫(huà)會(huì)消失,就算你重畫(huà)了,也會(huì)看到屏幕的某些部分先黑了下,然后恢復(fù)了。當(dāng)然如果你可以接受,那么就這樣吧。 然后,可以很自然的想到是誰(shuí)調(diào)用了這個(gè)函數(shù),從源頭把這個(gè)問(wèn)題消除掉。但是情況卻不是這樣的。我搜索這個(gè)函數(shù)名,找到了一個(gè) set_ctrlr_state()的函數(shù)調(diào)用了pxafb_disable_controller(),搜索set_ctrlr_state(),發(fā)現(xiàn) 有個(gè)pxafb_task()調(diào)用了set_ctrlr_state(),但是到了pxafb_task()就沒(méi)有辦法再往上找了,因?yàn)檫@是提供給內(nèi)核的 一個(gè)任務(wù),以指針傳遞函數(shù)入口。我對(duì)內(nèi)核了解太不夠了,花了很多時(shí)間看了很多論壇上的文章,機(jī)緣巧合之下,我找到了/drivers/char/vt.c 這個(gè)文件。vt.c我感覺(jué)應(yīng)該是2.4內(nèi)核的console.c和vt.c的結(jié)合體,應(yīng)為它集成了console基本上所有功能函數(shù),就ioctl在 vt_ioctl.c這個(gè)文件里。這個(gè)文件的主要作用是負(fù)責(zé)管理控制臺(tái),如控制臺(tái)的模式(圖形、字符)、向控制臺(tái)輸出等等。其中能找到一些如 do_blank_screen(),blank_screen_t()這樣的函數(shù),就是這些函數(shù)關(guān)閉了LCD控制器,修改任意一個(gè)都可以起作用。網(wǎng)上的 一個(gè)解決方案是把blank_screen_t()變成空函數(shù),但是我沒(méi)有這樣試過(guò),我覺(jué)得已經(jīng)來(lái)到了問(wèn)題的根源附近,應(yīng)該能從根本上解決。 我們先看下屏幕關(guān)閉問(wèn)題的真正起因,看這個(gè)控制臺(tái)初始化函數(shù) static int __init con_init(void){
?const char *display_desc = NULL;
?struct vc_data *vc;
?unsigned int currcons = 0; acquire_console_sem(); if (conswitchp)
??display_desc = conswitchp->con_startup();
?if (!display_desc) {
??fg_console = 0;
??release_console_sem();
??return 0;
?} ?init_timer(&console_timer);
?console_timer.function = blank_screen_t;?
?if (blankinterval) {
??blank_state = blank_normal_wait;
??mod_timer(&console_timer, jiffies + blankinterval);
?} // 這是對(duì)控制臺(tái)定時(shí)器的初始化,定時(shí)器事件函數(shù)被連接到了blank_screen_t() /*
? * kmalloc is not running yet - we use the bootmem allocator.
? */
?for (currcons = 0; currcons < MIN_NR_CONSOLES; currcons++) {
??vc_cons[currcons].d = vc = alloc_bootmem(sizeof(struct vc_data));
??visual_init(vc, currcons, 1);
??vc->vc_screenbuf = (unsigned short *)alloc_bootmem(vc->vc_screenbuf_size);
??vc->vc_kmalloced = 0;
??vc_init(vc, vc->vc_rows, vc->vc_cols,
???currcons || !vc->vc_sw->con_save_screen);
?}
?currcons = fg_console = 0;
?master_display_fg = vc = vc_cons[currcons].d;
?set_origin(vc);
?save_screen(vc);
?gotoxy(vc, vc->vc_x, vc->vc_y);
?csi_J(vc, 0);
?update_screen(vc);
?printk("Console: %s %s %dx%d",
??vc->vc_can_do_color ? "colour" : "mono",
??display_desc, vc->vc_cols, vc->vc_rows);
?printable = 1;
?printk("/n"); release_console_sem(); #ifdef CONFIG_VT_CONSOLE
?register_console(&vt_console_driver);
#endif
?return 0;
}
其中引用了一個(gè)叫blankinterval的全局變量和一個(gè)console_time,我不知道內(nèi)核的定時(shí)器是具體是怎么工作,但是 這樣的代碼已經(jīng)很明顯了。這個(gè)定時(shí)器和電源管理宏P(guān)M_CONFIG沒(méi)有任何關(guān)系,它是控制臺(tái)的一部分。再看下blank_screen_t(): static void blank_screen_t(unsigned long dummy)
{
?blank_timer_expired = 1;
?schedule_work(&console_work);
} 可以發(fā)現(xiàn)vt.c開(kāi)頭的宏,static DECLARE_WORK(console_work, console_callback, NULL);,找到了console_callback()這個(gè)函數(shù): static void console_callback(void *ignored)
{
?acquire_console_sem(); if (want_console >= 0) {
??if (want_console != fg_console &&
????? vc_cons_allocated(want_console)) {
???hide_cursor(vc_cons[fg_console].d);
???change_console(vc_cons[want_console].d);
???/* we only changed when the console had already
????? been allocated - a new console is not created
????? in an interrupt routine */
??}
??want_console = -1;
?}
?if (do_poke_blanked_console) { /* do not unblank for a LED change */
??do_poke_blanked_console = 0;
??poke_blanked_console();
?}
?if (scrollback_delta) {
??struct vc_data *vc = vc_cons[fg_console].d;
??clear_selection();
??if (vc->vc_mode == KD_TEXT)
???vc->vc_sw->con_scrolldelta(vc, scrollback_delta);
??scrollback_delta = 0;
?}
?if (blank_timer_expired) {
??do_blank_screen(0);
??blank_timer_expired = 0;
?} release_console_sem();
} 再看do_blank_screen(),隨著struct vc_data中的與fops類似指針跟蹤下去,就可以找到驅(qū)動(dòng)里面的相應(yīng)代碼了。寫出來(lái)太麻煩,讓我偷懶把。 小總結(jié)下,其實(shí)在控制臺(tái)內(nèi)部就有一個(gè)定時(shí)器,它負(fù)責(zé)在一定時(shí)間之后將顯示關(guān)閉,而無(wú)視是否打開(kāi)了電源管理功能。那這和Framebuffer有什么關(guān)系呢?我從一個(gè)很弱智的角度解釋,內(nèi)核剛啟動(dòng)的時(shí)候有這樣一句輸出: Console: colour dummy device 80x30 在初始化LCD控制器(Framebuffer)之后,有這樣一句輸出: Console: switching to colour frame buffer device 80x30 我就理解:這時(shí)候,內(nèi)核把控制臺(tái)(也不知道是console還是tty)切換到了Framebuffer上,大蝦們趕快跳出來(lái)批判我吧,呵呵。 回到正題,從代碼可以發(fā)現(xiàn),根本的解決之道是讓blankinterval = 0,blank_state就不會(huì)是blank_off之外的值,也就不會(huì)關(guān)閉屏幕了。 但是問(wèn)題到這里還是沒(méi)有完全解決,如果用戶程序希望改變blankinterval來(lái)實(shí)現(xiàn)屏保(當(dāng)然在我的系統(tǒng)上用不著);另外,一些程序改變 了blankinterval,程序退出之后,屏幕在一段時(shí)間之后還是會(huì)關(guān)閉的。怎么才能在用戶程序那頭解決這個(gè)問(wèn)題呢,這又耗費(fèi)了我很多時(shí)間。 我在追查代碼的過(guò)程中走了個(gè)彎路,認(rèn)為修改控制臺(tái)的模式可以不讓黑屏現(xiàn)象出現(xiàn),但是后來(lái)發(fā)現(xiàn),這樣可能會(huì)使控制臺(tái)沒(méi)有辦法畫(huà)圖,不知道對(duì)不對(duì)。 忽略彎路,直接正解。vt.c中不是有很多操作控制臺(tái)的函數(shù)么?看看是誰(shuí)修改了blankinterval。于是搜索 blankinterval,發(fā)現(xiàn)setterm_command()修改了它,然后搜索setterm_command,找到了 do_con_trol()函數(shù),搜索do_con_trol,找到了do_con_write()函數(shù),搜索do_con_write,終于最終 BOSS現(xiàn)身了:con_write()函數(shù)。為什么說(shuō)它是最終BOSS呢?看看這段: static struct tty_operations con_ops = {
?.open = con_open,
?.close = con_close,
?.write = con_write,
?.write_room = con_write_room,
?.put_char = con_put_char,
?.flush_chars = con_flush_chars,
?.chars_in_buffer = con_chars_in_buffer,
?.ioctl = vt_ioctl,
?.stop = con_stop,
?.start = con_start,
?.throttle = con_throttle,
?.unthrottle = con_unthrottle,
}; 熟悉fops的話你就能看出來(lái)了,這是對(duì)tty設(shè)備的文件操作函數(shù)的表。也就是說(shuō),在用戶程序里,通過(guò)open函數(shù)打開(kāi)/dev/tty,然后 再用write函數(shù)就可以修改blankinterval了。原理是找到了,實(shí)踐上有很大困難,那么多重函數(shù)調(diào)用,再看看do_con_trol()里面 的switch語(yǔ)句,正常人都要發(fā)暈。好在偉大的百度為我們提供了很多信息:在命令行下,可以使用setterm -blank 0指令來(lái)設(shè)置blankinterval。哈哈,救星來(lái)了,趕快看看setterm的源代碼。setterm屬于util-linux包,搜索一下很容易 找到。其中的perform_sequence()函數(shù)里有這樣一段: /* -blank [0-60]. */
?if (opt_blank && vcterm)?
??printf("/033[9;%d]", opt_bl_min); 真得很神奇啊,用個(gè)printf就可以在用戶程序里解決這個(gè)問(wèn)題,本來(lái)我是打算只說(shuō)用printf解決的,看到原理我想會(huì)更舒服一些;況且,在我的系統(tǒng)上用printf是不行的。 但是!問(wèn)題還沒(méi)有完,往往在我們的系統(tǒng)中,LCD的虛擬控制臺(tái)和控制臺(tái)TTY不是同一個(gè)設(shè)備,也就是說(shuō),如果在程序里單純的printf是不行的!這樣只能修改你正在使用的TTY的blankinterval,而你用的卻是文本方式的設(shè)備,不存在黑屏問(wèn)題。 于是,就需要仔細(xì)比較/dev/console、/dev/tty、/dev/ttyn的設(shè)備號(hào),在我的系統(tǒng)里,用戶程序里/dev /console和/dev/tty都是5,說(shuō)明他們是一個(gè)東西,/dev/ttyn是4,這才是FB上的虛擬控制臺(tái)。但是/dev/ttyn不是正在使 用的TTY,那么怎么printf呢?只好用write函數(shù)來(lái)解決了。 寫這樣一段代碼: #include <fcntl.h> #include <stdio.h> #include <sys/ioctl.h> void some_function() { int f; f = open("/dev/tty0", O_RDWR); write(f, "/033[9;0]", 8); close(f); } 問(wèn)題終于解決了。 總結(jié)下,第二個(gè)問(wèn)題有很多種解決方法: 1.修改LCD驅(qū)動(dòng),把關(guān)閉LCD控制器的函數(shù)變?yōu)榭?#xff08;不推薦) 2.修改vt.c中的blank_screen_t()函數(shù),讓其為空(在系統(tǒng)不需要使用關(guān)閉顯示功能時(shí)推薦) 3.修改vt.c中的blankinterval,讓其為0(系統(tǒng)可能需要使用關(guān)閉顯示功能,而且希望系統(tǒng)上電后正常狀態(tài)下不會(huì)關(guān)閉顯示時(shí)推薦) 4.修改用戶程序,加入設(shè)置blankinterval的代碼(推薦) 今天就寫到這里,繼續(xù)干活了。
printf("\033[9;0]");
就相當(dāng)于setterm?-blank?0
printf("\033[9;1]");
就相當(dāng)于setterm?-blank?1
而char?ar[2];
ar[0]=10;ar[1]=0;
ioctl(0,TIOCLINUX,ar);相當(dāng)于setterm?-powersave?off
setterm命令
1.功能作用
setterm用于設(shè)定TTY STREAMS環(huán)境。它可以查詢以及控制某一TTY通訊埠的STREAMS模組。
setterm讓使用者可以用系統(tǒng)內(nèi)建或使用者自備之STREAMS模組,來(lái)調(diào)整他們的TTY STREAMS環(huán)境。
2.位置
/usr/bin/setterm
3.格式用法
setterm [options]
4.主要參數(shù)
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 | -term <terminal_name> -reset -initialize -cursor <on|off> -repeat <on|off> -appcursorkeys <on|off> -linewrap <on|off> -default -foreground <black|blue|green|cyan|red|magenta|yellow|white|default> -background <black|blue|green|cyan|red|magenta|yellow|white|default> -ulcolor <black|grey|blue|green|cyan|red|magenta|yellow|white> -ulcolor <bright blue|green|cyan|red|magenta|yellow|white> -hbcolor <black|grey|blue|green|cyan|red|magenta|yellow|white> -hbcolor <bright blue|green|cyan|red|magenta|yellow|white> -inversescreen <on|off> -bold <on|off> -half-bright <on|off> -blink <on|off> -reverse <on|off> -underline <on|off> -store > -clear <all|rest> -tabs < tab1 tab2 tab3 ... >??????(tabn = 1-160) -clrtabs < tab1 tab2 tab3 ... >?? (tabn = 1-160) -regtabs <1-160> -blank <0-60|force|poke> -dump?? <1-NR_CONSOLES> -append <1-NR_CONSOLES> -file dumpfilename -msg <on|off> -msglevel <0-8> -powersave <on|vsync|hsync|powerdown|off> -powerdown <0-60> -blength <0-2000> -bfreq freqnumber -version -help |
5.應(yīng)用實(shí)例
1、可以用setterm程序來(lái)獲得控制臺(tái)下的屏幕截圖
setterm -dump 1
上面命令中,1指第一個(gè)虛擬控制臺(tái),如要獲得第二個(gè)虛擬控制臺(tái)的內(nèi)容,應(yīng)改為2,
2、關(guān)閉屏保
setterm -blank 0
3、設(shè)置屏保為1分鐘
setterm -blank 1
4、關(guān)閉、打開(kāi)光標(biāo)
setterm -cursor on|off
5、終端響鈴聲能使用setterm關(guān)閉
setterm -blength 0
6、在終端顯示加下劃線的文字
setterm -underline on
《新程序員》:云原生和全面數(shù)字化實(shí)踐50位技術(shù)專家共同創(chuàng)作,文字、視頻、音頻交互閱讀
總結(jié)
- 上一篇: PHP中htmlentities和htm
- 下一篇: linux下tty,控制台,虚拟终端,串