十分钟黑屏的问题
轉自:http://blog.csdn.net/lanmanck/article/details/5148917
又是好久沒有更新了,同樣記錄下這段時間干的事情。? 來到公司報道之后我就去了工廠,呆了一個多月,熟悉公司各種產品的生產過程。當然,就是和生產線上的小MM們聊聊天,哈哈,開玩笑了。這期間我 的導師給我了個任務——一個UI的低成本實現。這個UI必須具有LCD顯示、USB主機、網絡等功能,要求就是低成本低成本再低成本;很自然選型選完之后 就又變成搞ARM了。買了開發板,硬件就沒有什么好說的了。任務的重點落在了軟件上,剛開始我和導師達成的意見是代碼裸奔(省去授權費用),或者穿個自己 寫的小OS。于是我花了2個星期的時間寫了0.8(寫了任務切換,其他沒有最后寫完)個類似uCOS功能的小OS,發現這種東西跑在ARM920T上實在 發揮不出920T的功能,于是我又花了2個星期寫了存儲管理,打算加入MMU;但是寫完發現就算系統能工作,我怎么調試應用程序是個大問題,因為用了 MMU,用戶進程就不能和內核一起玩了。經過和導師的商量,我再次回到了Linux;目前的打算是使用免費的內核,自己寫個類似TC的圖形庫,或者使用 Microwindows + FLTK之類的不要錢的東西。很浪費時間啊,花了4個星期才發現原來自己寫系統真的是非常困難的。但是這4個星期也沒有白花,有些附屬產品,例如自己寫的 不需要stdlib庫的malloc。其實我是第二次寫這個東西了,第一次的不太成熟,用了幾次就發現局限性了。現在寫的這個是我啃了幾天操作系統原理寫 的,應該相對好用,以后有空我會貼出代碼來。 然后就是我做過的Linux開發過程了,其實說起來,這次開發我發現自己Linux真的什么都不懂。過去偷懶沒有花時間好好看看Linux內核,現在造成很多麻煩,今天這些問題,熟悉內核的人只需要1分鐘就能搞定了,我花了1天多。寫下這個筆記,以便以后查閱使用。 第一個問題:啟動Linux的時候LCD會全屏花屏大約0.5秒,然后左上角出現一塊不明花斑。 這個問題相對簡單。因為我在Bootloader里面打開了液晶顯示,緩沖區映射在某個地址上,當內核初始化MMU的時候,LCD控制寄存器里 緩沖區的位置信息就不對了,或者是Bootloader使用的緩沖區被內核的數據或代碼覆蓋,導致在內核初始化LCD之前,LCD花屏。 那個不明花斑其實是Linux的可愛小企鵝圖片,但是可能因為緩沖區像素位寬和格式、LCD調色板設置等問題顯示不出來。 花屏解決方法:在Bootloader加載系統之前關掉LCD控制器或者關掉LCD的背光,這樣做比較簡單。復雜的,就得改MMU映射部分代 碼,在修改了MMU映射之后,立即修改LCD緩沖的位置。反正我就關掉LCD控制器了,因為內核很快就會初始化LCD,Windows啟動都黑屏呢,我們 黑那么2秒鐘也沒有什么大不了的。 花斑解決方法:相信如果做產品的話,不需要顯示什么企鵝給用戶看,所以可以在內核選項里將這個企鵝logo關掉。具體位置在Device Drivers>Graphics support>Logo configuration,對應的宏相信在.config里面很容易找到。如果非要顯示這個企鵝,那么可以在/drivers/video /console/fbcon.c里面找找,我也不知道怎么弄。 第二個問題:Linux啟動之后,只要一段時間不動鍵盤(開發板上用IO擴展出來的鍵盤),LCD就會自動關閉(黑屏、顯示慢慢消失之類),只要按下鍵盤就能恢復。 這個問題讓我花了一天多的時間。其實如果是手持設備,這樣也沒有什么。但是我們公司的產品是要一直顯示東西的,必須解決這個問題。我看了很多論 壇,有不少人也遇到了這個問題,但是我剛才是搜索的時候,關鍵詞不對,總找不到正確的答案。如果你遇到了同樣的問題,而且不想看我的三腳貓分析,那么就在 百度上搜索“blankinterval”、“setterm -blank 0”之類的,馬上你就能找到簡單的解決方案。 這個問題很容易讓人想到屏幕保護和電源管理。的確,這是一種電源管理。但是,你卻無法從Linux內核選項的電源管理中解決這個問題。我們一步步來。 首先,我測量到LCD的PCLK時鐘消失了,這意味著內核把LCD控制器關掉了。于是,從LCD驅動程序著手。我用的是S3C2440,這是 2410的升級版,但是LCD控制器是一樣的,在我拿到的開發板廠商給我做好驅動的內核里,驅動的位置在/drivers/video /s3c2410fb.c。為什么后面有個fb呢?這是Framebuffer的縮寫,百度下你能找到很多關于它的解釋。Framebuffer是所有 Linux下GUI程序對硬件操作的設備接口,位于/dev中,一般為fb0。在s3c2410fb.c中可以找到一個類似 s3c2410_disable_controller()這樣名稱的函數,我的驅動里叫pxafb_disable_controller(),可以看 出這個驅動是從pxa處理器改的,當然廠家不一樣名字也叫得不一樣。里面有一句類似這樣寫的 __raw_writel(fbi->reg.lcdcon1 & ~S3C2410_LCDCON1_ENVID, S3C2410_LCDCON1);,把這句話刪掉LCD就不會關掉了。這是第一個層次,我也看到有人是這樣做的。但是,這有問題,按鍵盤恢復后,原本顯 示在屏幕上的東西如果你不重畫會消失,就算你重畫了,也會看到屏幕的某些部分先黑了下,然后恢復了。當然如果你可以接受,那么就這樣吧。 然后,可以很自然的想到是誰調用了這個函數,從源頭把這個問題消除掉。但是情況卻不是這樣的。我搜索這個函數名,找到了一個 set_ctrlr_state()的函數調用了pxafb_disable_controller(),搜索set_ctrlr_state(),發現 有個pxafb_task()調用了set_ctrlr_state(),但是到了pxafb_task()就沒有辦法再往上找了,因為這是提供給內核的 一個任務,以指針傳遞函數入口。我對內核了解太不夠了,花了很多時間看了很多論壇上的文章,機緣巧合之下,我找到了/drivers/char/vt.c 這個文件。vt.c我感覺應該是2.4內核的console.c和vt.c的結合體,應為它集成了console基本上所有功能函數,就ioctl在 vt_ioctl.c這個文件里。這個文件的主要作用是負責管理控制臺,如控制臺的模式(圖形、字符)、向控制臺輸出等等。其中能找到一些如 do_blank_screen(),blank_screen_t()這樣的函數,就是這些函數關閉了LCD控制器,修改任意一個都可以起作用。網上的 一個解決方案是把blank_screen_t()變成空函數,但是我沒有這樣試過,我覺得已經來到了問題的根源附近,應該能從根本上解決。 我們先看下屏幕關閉問題的真正起因,看這個控制臺初始化函數 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);
?} // 這是對控制臺定時器的初始化,定時器事件函數被連接到了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;
}
其中引用了一個叫blankinterval的全局變量和一個console_time,我不知道內核的定時器是具體是怎么工作,但是 這樣的代碼已經很明顯了。這個定時器和電源管理宏PM_CONFIG沒有任何關系,它是控制臺的一部分。再看下blank_screen_t(): static void blank_screen_t(unsigned long dummy)
{
?blank_timer_expired = 1;
?schedule_work(&console_work);
} 可以發現vt.c開頭的宏,static DECLARE_WORK(console_work, console_callback, NULL);,找到了console_callback()這個函數: 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類似指針跟蹤下去,就可以找到驅動里面的相應代碼了。寫出來太麻煩,讓我偷懶把。 小總結下,其實在控制臺內部就有一個定時器,它負責在一定時間之后將顯示關閉,而無視是否打開了電源管理功能。那這和Framebuffer有什么關系呢?我從一個很弱智的角度解釋,內核剛啟動的時候有這樣一句輸出: Console: colour dummy device 80x30 在初始化LCD控制器(Framebuffer)之后,有這樣一句輸出: Console: switching to colour frame buffer device 80x30 我就理解:這時候,內核把控制臺(也不知道是console還是tty)切換到了Framebuffer上,大蝦們趕快跳出來批判我吧,呵呵。 回到正題,從代碼可以發現,根本的解決之道是讓blankinterval = 0,blank_state就不會是blank_off之外的值,也就不會關閉屏幕了。 但是問題到這里還是沒有完全解決,如果用戶程序希望改變blankinterval來實現屏保(當然在我的系統上用不著);另外,一些程序改變 了blankinterval,程序退出之后,屏幕在一段時間之后還是會關閉的。怎么才能在用戶程序那頭解決這個問題呢,這又耗費了我很多時間。 我在追查代碼的過程中走了個彎路,認為修改控制臺的模式可以不讓黑屏現象出現,但是后來發現,這樣可能會使控制臺沒有辦法畫圖,不知道對不對。 忽略彎路,直接正解。vt.c中不是有很多操作控制臺的函數么?看看是誰修改了blankinterval。于是搜索 blankinterval,發現setterm_command()修改了它,然后搜索setterm_command,找到了 do_con_trol()函數,搜索do_con_trol,找到了do_con_write()函數,搜索do_con_write,終于最終 BOSS現身了:con_write()函數。為什么說它是最終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的話你就能看出來了,這是對tty設備的文件操作函數的表。也就是說,在用戶程序里,通過open函數打開/dev/tty,然后 再用write函數就可以修改blankinterval了。原理是找到了,實踐上有很大困難,那么多重函數調用,再看看do_con_trol()里面 的switch語句,正常人都要發暈。好在偉大的百度為我們提供了很多信息:在命令行下,可以使用setterm -blank 0指令來設置blankinterval。哈哈,救星來了,趕快看看setterm的源代碼。setterm屬于util-linux包,搜索一下很容易 找到。其中的perform_sequence()函數里有這樣一段: /* -blank [0-60]. */
?if (opt_blank && vcterm)?
??printf("/033[9;%d]", opt_bl_min); 真得很神奇啊,用個printf就可以在用戶程序里解決這個問題,本來我是打算只說用printf解決的,看到原理我想會更舒服一些;況且,在我的系統上用printf是不行的。 但是!問題還沒有完,往往在我們的系統中,LCD的虛擬控制臺和控制臺TTY不是同一個設備,也就是說,如果在程序里單純的printf是不行的!這樣只能修改你正在使用的TTY的blankinterval,而你用的卻是文本方式的設備,不存在黑屏問題。 于是,就需要仔細比較/dev/console、/dev/tty、/dev/ttyn的設備號,在我的系統里,用戶程序里/dev /console和/dev/tty都是5,說明他們是一個東西,/dev/ttyn是4,這才是FB上的虛擬控制臺。但是/dev/ttyn不是正在使 用的TTY,那么怎么printf呢?只好用write函數來解決了。 寫這樣一段代碼: #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); } 問題終于解決了。 總結下,第二個問題有很多種解決方法: 1.修改LCD驅動,把關閉LCD控制器的函數變為空(不推薦) 2.修改vt.c中的blank_screen_t()函數,讓其為空(在系統不需要使用關閉顯示功能時推薦) 3.修改vt.c中的blankinterval,讓其為0(系統可能需要使用關閉顯示功能,而且希望系統上電后正常狀態下不會關閉顯示時推薦) 4.修改用戶程序,加入設置blankinterval的代碼(推薦) 今天就寫到這里,繼續干活了。
printf("\033[9;0]");
就相當于setterm?-blank?0
printf("\033[9;1]");
就相當于setterm?-blank?1
而char?ar[2];
ar[0]=10;ar[1]=0;
ioctl(0,TIOCLINUX,ar);相當于setterm?-powersave?off
setterm命令
1.功能作用
setterm用于設定TTY STREAMS環境。它可以查詢以及控制某一TTY通訊埠的STREAMS模組。
setterm讓使用者可以用系統內建或使用者自備之STREAMS模組,來調整他們的TTY STREAMS環境。
2.位置
/usr/bin/setterm
3.格式用法
setterm [options]
4.主要參數
| 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.應用實例
1、可以用setterm程序來獲得控制臺下的屏幕截圖
setterm -dump 1
上面命令中,1指第一個虛擬控制臺,如要獲得第二個虛擬控制臺的內容,應改為2,
2、關閉屏保
setterm -blank 0
3、設置屏保為1分鐘
setterm -blank 1
4、關閉、打開光標
setterm -cursor on|off
5、終端響鈴聲能使用setterm關閉
setterm -blength 0
6、在終端顯示加下劃線的文字
setterm -underline on
《新程序員》:云原生和全面數字化實踐50位技術專家共同創作,文字、視頻、音頻交互閱讀
總結
- 上一篇: PHP中htmlentities和htm
- 下一篇: linux下tty,控制台,虚拟终端,串