一.關(guān)于H-sync /V-Sync的知識:
1. 分辨率:比如說640x480,就會有640 個pixel &480 line,那么每個V-sync的信號時間內(nèi)就會有480個H-sync,而一個H-sync會有640個pixel。但是,每個pixel會有2 byte,所以我們會量到PCLK 在一個H-sync內(nèi)的數(shù)量會有1280個。
2. H-sync /V-Sync的極性polarity: polarity就是資料有效的準(zhǔn)備,比方說V-sync上的H-sync有可能在V-sync的low,也有可能在high出現(xiàn)。
二. 所使用的ISP處理器簡介:XXX838是一款isp(圖像信號處理器)ic,核心是一款arm7 process,提供自動對焦,人臉識別等功能。BB通過i2c與其進行命令類的數(shù)據(jù)通信,而sensor數(shù)據(jù)則通過CCIR總線傳輸給BB.
三. 25平臺 camera處理流程學(xué)習(xí)
1. void cam_event_ind_hdlr(ilm_struct *ilm_ptr)// This function is to handle camera event indication.
在該函數(shù)中,通過camera_capture_mem_process(&capture_mem_param)命令從lcd層獲取capture數(shù)據(jù),然后通過jpeg_encode_process(&jpg_encode)命令將這些數(shù)據(jù)軟編碼成jpeg格式的數(shù)據(jù)。
2. void cam_capture_req_hdlr(ilm_struct *ilm_ptr)// This function is to handle camera capture request.
(1) 該函數(shù)首先執(zhí)行exit_camera_preview_process();命令退出preview流程;
(2) ./* copy preview data to MMI buffer */
memcpy(
(kal_uint8*) cam_context_p->frame_buffer_p,
(kal_uint8*) cam_context_p->int_frame_buffer_p,
cam_context_p->frame_buffer_size);
(3). /* release preview related memory */
cam_release_memory();
3.cam_context_p->capture_buffer_p :所需存儲的拍照數(shù)據(jù)指針
cam_context_p->file_size :所要存儲的數(shù)據(jù)大小
4. 在cam_open_image_file函數(shù)中執(zhí)行命令
cam_context_p->capture_buffer_p = (kal_uint32) med_alloc_ext_mem(buffer_size);
來分配內(nèi)存。
Capture數(shù)據(jù)存儲指針:capture_isp_param.target_buffer_start_address = (kal_uint32) cam_context_p->capture_buffer_p;
camera_capture_jpeg_process(&capture_isp_param);
isp_capture_jpeg_data.target_buffer_start_address=isp_data->target_buffer_start_address;
sw_jpeg_encode_config_data.jpeg_file_start_address=isp_capture_jpeg_data.target_buffer_start_address;
5. camera capture后的數(shù)據(jù)傳送流程:cam_context_p->intmem_start_address.
(1). capture_isp_param.intmem_start_address = cam_context_p->intmem_start_address =
(kal_uint32) med_alloc_int_mem(cam_capture_mem[0]);//只有45k
capture_isp_param.intmem_size = cam_context_p->intmem_size = (kal_uint32) cam_capture_mem[0];
(2). file_size = camera_capture_jpeg_process(&capture_isp_param); //jpeg編碼后的文件大小
(3).isp_capture_jpeg_data.intmem_start_address=isp_data->intmem_start_address;
isp_capture_jpeg_data.intmem_size=isp_data->intmem_size;
(4). intmem_init((kal_uint32 *) isp_capture_jpeg_data.intmem_start_address,
isp_capture_jpeg_data.intmem_size);
(5).sw_jpeg_encode_config_data.intmem_start_address=isp_capture_jpeg_data.intmem_start_address; //將所獲取的capture原始數(shù)據(jù)地址指針賦給軟編碼的起始地址
四.調(diào)試關(guān)鍵點
1. 首先調(diào)通I2C通訊,必須確保BB與ISP的I2C通訊正常;
2. 重新定義#define MAX_CAM_FILE_BUFFER_LEN (3150*1024) /* 2700kb for 5.0M */
3.仿照camera_capture_jpeg_process函數(shù),創(chuàng)建一個新函數(shù),在該函數(shù)中對獲取的數(shù)據(jù)直接存儲,而不經(jīng)過jpeg編碼流程(由于XXX838傳輸過來的已經(jīng)是jpeg格式的數(shù)據(jù))。
注意:
(1) 在該函數(shù)中,要設(shè)置:
ENABLE_CAMERA_OUTPUT_TO_MEM;//ISP輸出至Memory,
SET_CAMERA_CAPTURE_MODE
/*** Capture,等待VSYNC中斷**/
(2).在capture完成后,要DISABLE_CMOS_SESNOR;//關(guān)閉sensor信號。
(3).此時,獲取的capture的數(shù)據(jù)已經(jīng)存儲在isp_data->target_buffer_start_address中;
然后讀取這些數(shù)據(jù),通過0xff ,0xd8判斷文件頭,0xff ,0xd9判斷jpeg文件尾及其長度。
(4)最后,通過kal_int32 cam_close_image_file(kal_uint32 size)保存文件
一、協(xié)商和解
協(xié)商和解的定義
????消費者與經(jīng)營者在發(fā)生爭議后,就與爭議有關(guān)的問題進行協(xié)商,在自愿、互諒的基礎(chǔ)上,通過直接對話擺事實、講道理,分清責(zé)任,達成和解協(xié)議,使糾紛得以解決的活動。消費者權(quán)益爭議的協(xié)商和解是一種快速、簡便的爭議解決方式,無論是對消費者還是對經(jīng)營者,它都不失為一種理想的途徑。事實上,日常生活中大量的消費者權(quán)益爭議都是通過這種方式解決的。
消費者與經(jīng)營者協(xié)商和解的法律依據(jù)
????《消費者權(quán)益保護法》第34條明確規(guī)定:"消費者和經(jīng)營者發(fā)生消費者權(quán)益爭議的,可以通過下列途徑解決:(1)與經(jīng)營者協(xié)商和解;(2)請求消費者協(xié)會調(diào)解;(3)向有關(guān)行政部門申訴;(4)根據(jù)與經(jīng)營者達成的仲裁協(xié)議提請仲裁機構(gòu)仲裁;(5)向人民法院提起訴訟。"此條第1項規(guī)定,即"與經(jīng)營者協(xié)商和解",便是消費者與經(jīng)營者協(xié)商和解的法律依據(jù)。
協(xié)商和解的步驟
????在實踐中,協(xié)商和解可以在其權(quán)益受到侵犯時,帶上有關(guān)證據(jù),如購貨憑證或者服務(wù)單據(jù)以及受損失證據(jù),找到經(jīng)營者,向其負(fù)責(zé)人或者主管解決糾紛的部門說明情況,并提出自己的意見和要求。如果經(jīng)營者覺得消費者的意見和要求合理,就會接受。如果經(jīng)營者覺得消費者的要求過高,就會要求消費者降低其要求。經(jīng)過一番"討價還價"后,達成一個雙方都愿意接受的協(xié)議時,爭議就解決了。
協(xié)商和解應(yīng)堅持協(xié)作和平等原則:
????協(xié)作原則。要求消費者與經(jīng)營者在融洽的氣氛中,在互相諒解的基礎(chǔ)上,本著實事求是、團結(jié)協(xié)作的精神,通過擺事實講道理,弄清事實,分清責(zé)任,自愿地達成協(xié)議,避免只從自己一方的利益出發(fā),堅持已見,互不相讓。
????平等原則。消費者和經(jīng)營者要在平等的前提下自行協(xié)商解決消費者權(quán)益爭議。決不允許任何一方憑借某種勢力,以強凌弱,以大壓小,享有特權(quán),獲得不平等的利益。
在協(xié)商和解時,消費者應(yīng)注意以下問題:
????針對經(jīng)營者故意拖延或無理拒絕消費者協(xié)商和解建議的行為,消費者應(yīng)立即采取措施,用其他途徑解決爭議問題。即可用投訴、申訴或仲裁、起訴手段解決糾紛。如果經(jīng)營者的故意拖延和無理拒絕,致使消費者財產(chǎn)損失擴大的,經(jīng)營者除了應(yīng)當(dāng)滿足消費者正常要求外,還應(yīng)當(dāng)就擴大的損失承擔(dān)賠償責(zé)任。
????針對經(jīng)營者故意推卸責(zé)任,認(rèn)為產(chǎn)品出現(xiàn)質(zhì)量問題是生產(chǎn)廠家的事,要求消費者直接找廠家交涉的行為,按《消費者權(quán)益保護法》第35條規(guī)定:"消費者在購買、使用商品時,其合法權(quán)益受到損害的,可以向銷售者要求賠償。銷售者賠償后,屬于生產(chǎn)者的責(zé)任或者屬于向銷售者提供商品的其他銷售者的責(zé)任的,銷售者有權(quán)向生產(chǎn)者或者其他銷售者追償。消費者或者其他受害人因商品缺陷造成人身、財產(chǎn)損害的,可以向銷售者要求賠償,也可以向生產(chǎn)者要求賠償。屬于生產(chǎn)者責(zé)任的,銷售者賠償后,有權(quán)向生產(chǎn)者追償。屬于銷售者責(zé)任的,生產(chǎn)者賠償后,有權(quán)向銷售者追償。消費者在接受服務(wù)時,其合法權(quán)益受到損害的,可以向服務(wù)者要求賠償。"因此,當(dāng)消費者遇到商品質(zhì)量問題時,如經(jīng)營者推卸責(zé)任,認(rèn)為是生產(chǎn)廠家的問題,要求消費者直接找廠家交涉時,消費者應(yīng)當(dāng)有自我保護意識,不能挾在中間讓廠家和經(jīng)營者當(dāng)"皮球"踢。要以法律規(guī)定為依據(jù),切實維護自己的合法權(quán)益。
????針對經(jīng)營者以店堂通知、聲明、告示為由,拒不承擔(dān)責(zé)任的行為,按《消費者權(quán)益保護法》第24條規(guī)定:"經(jīng)營者不得以格式合同、通知、聲明、店堂告示等方式作出對消費者不公平、不合理的規(guī)定,或者減輕、免除其損害消費者合法權(quán)益應(yīng)當(dāng)承擔(dān)的民事責(zé)任。格式合同、通知、聲明、店堂告示等含有前款所列內(nèi)容的,其內(nèi)容無效。"因此,當(dāng)消費者因商品質(zhì)量和服務(wù)問題與商家交涉、協(xié)商時,千萬不能為其店堂內(nèi)服務(wù)規(guī)則或商品銷售告示所約束,這些服務(wù)規(guī)則與法無據(jù),沒有法律效力,應(yīng)視為無效規(guī)則。
二、投訴和調(diào)解
投訴的定義:
????消費者投訴,是指消費者為生活消費需要購買、使用商品或者接受服務(wù),與經(jīng)營者之間發(fā)生消費者權(quán)益爭議后,請求消費者權(quán)益保護組織調(diào)解,要求保護其合法權(quán)益的行為。
調(diào)解的含義:
????調(diào)解,即由第三方對爭議雙方當(dāng)事人進行說服勸導(dǎo)、勾通調(diào)和,以促成爭議雙方達成解決糾紛的協(xié)議的活動。
《消費者權(quán)益保護法》規(guī)定,消費者爭議可以通過消費者協(xié)會調(diào)解解決。實際上,消費者糾紛的調(diào)解并非只能由消費者協(xié)會進行,任何第三人參與消費者糾紛的解決,促成爭議雙方達成協(xié)議的,都屬調(diào)解的范圍。并且只要不存在違法行為,則調(diào)解同樣受法律承認(rèn)。
調(diào)解的原則:
????1、自愿原則。調(diào)解應(yīng)建立在雙方自愿的基礎(chǔ)之上。調(diào)解不同于審判,當(dāng)任何一方不同意調(diào)解時,應(yīng)終止調(diào)解,而不得以任何理由加以強迫。
????2、合法原則。調(diào)解活動應(yīng)在合法的原則上進行,既要有必要的靈活性,更要有高度的原則性,不能違反法律的規(guī)定來"和稀泥"。
投訴的形式:
????消費者投訴可以采取電話、信函、面談、互聯(lián)網(wǎng)形式進行。但無論采取哪種形式,都要講清楚以下內(nèi)容:一是投訴人基本情況。即投訴人的姓名、性別、聯(lián)系地址、聯(lián)系電話、郵政編碼等。二是被投訴方的基本情況。即被投訴方名稱、地址、電話等。三是購買商品的時間、品牌、產(chǎn)地、規(guī)格、數(shù)量、價格等。四是受損害的具體情況、發(fā)現(xiàn)問題的時間及與經(jīng)營者交涉的經(jīng)過等。五是購物憑證、保修卡、約定書復(fù)印件等。
三、行政申訴
申訴的定義
????消費者和經(jīng)營者發(fā)生權(quán)益爭議后,可以請求政府有關(guān)行政部門依行政程序解決爭議,與其他爭議解決途徑相比,申訴具有高效、快捷、力度強等特點。?
消費者向政府有關(guān)行政部門申訴的法律依據(jù)
???《消費者權(quán)益保護法》第34條的規(guī)定,消費者和經(jīng)營者發(fā)生消費者權(quán)益爭議的,可以向有關(guān)行政部門申訴。
消費者如何進行申訴?
????消費者決定申訴時,應(yīng)依照商品和服務(wù)的性質(zhì)向具有相關(guān)職能的行政部門提出。消費者申訴一般應(yīng)采用書面形式,一式兩份,并載明下列事項:(1)消費者的姓名、住址、電話號碼、郵政編碼;(2)被申訴人的名稱、地址、聯(lián)系電話、郵政編碼;(3)申訴的要求、理由及相關(guān)的事實根據(jù);(4)申訴的日期。必要時,消費者可委托代理人進行申訴活動,但需向有關(guān)行政部門提交授權(quán)委托書。
????消費者向有關(guān)行政部門提出申訴后,如果與經(jīng)營者協(xié)商和解,達成和解協(xié)議的,可以撤回申訴,請求有關(guān)行政部門根據(jù)和解協(xié)議作出調(diào)解書。如果與經(jīng)營者達成仲裁協(xié)議,可以撤回申訴,向仲裁機構(gòu)提請仲裁。如果想通過法律途徑解決,可以撤回申訴,向人民法院提起訴訟。
四、提請仲裁
仲裁的定義?
????雙方當(dāng)事人在爭議發(fā)生前或者爭議發(fā)生后達成的協(xié)議,自愿將他們之間的爭議提交雙方所同意的仲裁機構(gòu)居中調(diào)解,作出判斷或裁決的活動。
仲裁的優(yōu)越性
仲裁具有當(dāng)事人意思自愿、程序簡便、一裁終局、專家仲裁、費用較低、保守機密、相互感情影響小等特征。
當(dāng)事人采取仲裁方式解決糾紛,應(yīng)注意以下幾點:
(1)當(dāng)事人采用仲裁方式解決糾紛,應(yīng)當(dāng)是雙方自愿,并達成仲裁協(xié)議;
(2)向哪個仲裁組織提請仲裁,由當(dāng)事人協(xié)議選定;
(3)可以選擇或者委托仲裁組織指定仲裁員;
(4)可以自行和解,達成和解協(xié)議的,可以請求仲裁庭根據(jù)和解協(xié)議作出裁決書,也可以撤回仲裁申請。
仲裁的原則和制度
仲裁實行自愿、獨立、公正、一裁終局的原則和制度。
仲裁協(xié)議的定義
????雙方當(dāng)事人自愿把他們之間的經(jīng)濟爭議提交仲裁解決的書面約定。其表現(xiàn)形式包括合同中訂立的仲裁條款和以其他書面方式在糾紛發(fā)生后前或者發(fā)生后達成請求的仲裁協(xié)議。仲裁協(xié)議是獨立存在的,合同的變更、解除、終止或者無效,不影響仲裁協(xié)議的效力。
仲裁協(xié)議應(yīng)具備的內(nèi)容
(1)請求仲裁的意思表示;
(2)仲裁事項;
(3)選定的仲裁委員會。
當(dāng)事人提請仲裁應(yīng)當(dāng)符合的條件
(1)有仲裁協(xié)議;
(2)有具體的仲裁請求和事實理由;
(3)屬于仲裁委員會的管理范圍。
仲裁案件受理費的承擔(dān)
????仲裁費用原則上由敗訴的當(dāng)事人承擔(dān),當(dāng)事人部分勝訴,部分?jǐn)≡V的,由仲裁庭根據(jù)當(dāng)事人各方責(zé)任大小確定其各自應(yīng)當(dāng)承擔(dān)的仲裁費用的比例,當(dāng)事人自行和解或者經(jīng)仲裁庭調(diào)解結(jié)案的,當(dāng)事人可以協(xié)商確定各自承擔(dān)的仲裁費用的比例。
五、提起訴訟
提起訴訟的定義
????消費者因其合法權(quán)益受到侵害后,可以向人民法院提起訴訟,請求人民法院依照法定程序進行審判。在我國,訴訟大致分為三種形式:(1)刑事訴訟;(2)民事訴訟;(3)行政訴訟。消費者因其合法權(quán)益受到侵害而提起的訴訟屬于民事訴訟范疇。
提起訟訴必須具備的法定條件
(1)原告必須是與本案有直接利害關(guān)系的公民、法人和其他組織;
(2)有明確的被告;
(3)有具體的訴訟請求和事實、理由;
(1)屬于人民法院受理民事訴訟的范圍和受訴人民法院管轄。
符合以上條件的起訴,人民法院才會予以受理。
#define MIN(A,B) ((A) <= (B) (A) : (B))
這個測試是為下面的目的而設(shè)的:
1). 標(biāo)識#define在宏中應(yīng)用的基本知識。這是很重要的,因為直到嵌入(inline)操作符變?yōu)闃?biāo)準(zhǔn)C的一部分,宏是方便產(chǎn)生嵌入代碼的唯一方法,對于嵌入式系統(tǒng)來說,為了能達到要求的性能,嵌入代碼經(jīng)常是必須的方法。
2). 三重條件操作符的知識。這個操作符存在C語言中的原因是它使得編譯器能產(chǎn)生比if-then-else更優(yōu)化的代碼,了解這個用法是很重要的。
3). 懂得在宏中小心地把參數(shù)用括號括起來
4). 我也用這個問題開始討論宏的副作用,例如:當(dāng)你寫下面的代碼時會發(fā)生什么事?
least = MIN(*p++, b);
3. 預(yù)處理器標(biāo)識#error的目的是什么?
如果你不知道答案,請看參考文獻1。這問題對區(qū)分一個正常的伙計和一個書呆子是很有用的。只有書呆子才會讀C語言課本的附錄去找出象這種
問題的答案。當(dāng)然如果你不是在找一個書呆子,那么應(yīng)試者最好希望自己不要知道答案。
死循環(huán)(Infinite loops)
4. 嵌入式系統(tǒng)中經(jīng)常要用到無限循環(huán),你怎么樣用C編寫死循環(huán)呢?
這個問題用幾個解決方案。我首選的方案是:
while(1) { }
一些程序員更喜歡如下方案:
for(;;) { }
這個實現(xiàn)方式讓我為難,因為這個語法沒有確切表達到底怎么回事。如果一個應(yīng)試者給出這個作為方案,我將用這個作為一個機會去探究他們這樣做的
基本原理。如果他們的基本答案是:“我被教著這樣做,但從沒有想到過為什么。”這會給我留下一個壞印象。
第三個方案是用 goto
Loop:
...
goto Loop;
應(yīng)試者如給出上面的方案,這說明或者他是一個匯編語言程序員(這也許是好事)或者他是一個想進入新領(lǐng)域的BASIC/FORTRAN程序員。
數(shù)據(jù)聲明(Data declarations)
5. 用變量a給出下面的定義
a) 一個整型數(shù)(An integer)
b) 一個指向整型數(shù)的指針(A pointer to an integer)
c) 一個指向指針的的指針,它指向的指針是指向一個整型數(shù)(A pointer to a pointer to an integer)
d) 一個有10個整型數(shù)的數(shù)組(An array of 10 integers)
e) 一個有10個指針的數(shù)組,該指針是指向一個整型數(shù)的(An array of 10 pointers to integers)
f) 一個指向有10個整型數(shù)數(shù)組的指針(A pointer to an array of 10 integers)
g) 一個指向函數(shù)的指針,該函數(shù)有一個整型參數(shù)并返回一個整型數(shù)(A pointer to a function that takes an integer as an argument and returns an integer)
h) 一個有10個指針的數(shù)組,該指針指向一個函數(shù),該函數(shù)有一個整型參數(shù)并返回一個整型數(shù)( An array of ten pointers to functions that take an integer argument and return an integer )
答案是:
a) int a; // An integer
b) int *a; // A pointer to an integer
c) int **a; // A pointer to a pointer to an integer
d) int a[10]; // An array of 10 integers
e) int *a[10]; // An array of 10 pointers to integers
f) int (*a)[10]; // A pointer to an array of 10 integers
g) int (*a)(int); // A pointer to a function a that takes an integer argument and returns an integer
h) int (*a[10])(int); // An array of 10 pointers to functions that take an integer argument and return an integer
人們經(jīng)常聲稱這里有幾個問題是那種要翻一下書才能回答的問題,我同意這種說法。當(dāng)我寫這篇文章時,為了確定語法的正確性,我的確查了一下書。
但是當(dāng)我被面試的時候,我期望被問到這個問題(或者相近的問題)。因為在被面試的這段時間里,我確定我知道這個問題的答案。應(yīng)試者如果不知道
所有的答案(或至少大部分答案),那么也就沒有為這次面試做準(zhǔn)備,如果該面試者沒有為這次面試做準(zhǔn)備,那么他又能為什么出準(zhǔn)備呢?
Static
6. 關(guān)鍵字static的作用是什么?
這個簡單的問題很少有人能回答完全。在C語言中,關(guān)鍵字static有三個明顯的作用:
1). 在函數(shù)體,一個被聲明為靜態(tài)的變量在這一函數(shù)被調(diào)用過程中維持其值不變。
2). 在模塊內(nèi)(但在函數(shù)體外),一個被聲明為靜態(tài)的變量可以被模塊內(nèi)所用函數(shù)訪問,但不能被模塊外其它函數(shù)訪問。它是一個本地的全局變量。
3). 在模塊內(nèi),一個被聲明為靜態(tài)的函數(shù)只可被這一模塊內(nèi)的其它函數(shù)調(diào)用。那就是,這個函數(shù)被限制在聲明它的模塊的本地范圍內(nèi)使用。
大多數(shù)應(yīng)試者能正確回答第一部分,一部分能正確回答第二部分,同是很少的人能懂得第三部分。這是一個應(yīng)試者的嚴(yán)重的缺點,因為他顯然不懂得本地化數(shù)據(jù)和代碼范圍的好處和重要性。
Const
7.關(guān)鍵字const是什么含意?
我只要一聽到被面試者說:“const意味著常數(shù)”,我就知道我正在和一個業(yè)余者打交道。去年Dan Saks已經(jīng)在他的文章里完全概括了const的所有用法,因此ESP(譯者:Embedded Systems Programming)的每一位讀者應(yīng)該非常熟悉const能做什么和不能做什么.如果你從沒有讀到那篇文章,只要能說出const意味著“只讀”就可以了。盡管這個答案不是完全的答案,但我接受它作為一個正確的答案。(如果你想知道更詳細(xì)的答案,仔細(xì)讀一下Saks的文章吧。)如果應(yīng)試者能正確回答這個問題,我將問他一個附加的問題:下面的聲明都是什么意思?
const int a;
int const a;
const int *a;
int * const a;
int const * a const;
前兩個的作用是一樣,a是一個常整型數(shù)。第三個意味著a是一個指向常整型數(shù)的指針(也就是,整型數(shù)是不可修改的,但指針可以)。第四個意思a是一個指向整型數(shù)的常指針(也就是說,指針指向的整型數(shù)是可以修改的,但指針是不可修改的)。最后一個意味著a是一個指向常整型數(shù)的常指針(也就是說,指針指向的整型數(shù)是不可修改的,同時指針也是不可修改的)。如果應(yīng)試者能正確回答這些問題,那么他就給我留下了一個好印象。順帶提一句,也許你可能會問,即使不用關(guān)鍵字const,也還是能很容易寫出功能正確的程序,那么我為什么還要如此看重關(guān)鍵字const呢?我也如下的幾下理由:
1). 關(guān)鍵字const的作用是為給讀你代碼的人傳達非常有用的信息,實際上,聲明一個參數(shù)為常量是為了告訴了用戶這個參數(shù)的應(yīng)用目的。如果你曾花很多時間清理其它人留下的垃圾,你就會很快學(xué)會感謝這點多余的信息。(當(dāng)然,懂得用const的程序員很少會留下的垃圾讓別人來清理的。)
2). 通過給優(yōu)化器一些附加的信息,使用關(guān)鍵字const也許能產(chǎn)生更緊湊的代碼。
3). 合理地使用關(guān)鍵字const可以使編譯器很自然地保護那些不希望被改變的參數(shù),防止其被無意的代碼修改。簡而言之,這樣可以減少bug的出現(xiàn)。
Volatile
8. 關(guān)鍵字volatile有什么含意 并給出三個不同的例子。
一個定義為volatile的變量是說這變量可能會被意想不到地改變,這樣,編譯器就不會去假設(shè)這個變量的值了。精確地說就是,優(yōu)化器在用到這個變量時必須每次都小心地重新讀取這個變量的值,而不是使用保存在寄存器里的備份。下面是volatile變量的幾個例子:
1). 并行設(shè)備的硬件寄存器(如:狀態(tài)寄存器)
2). 一個中斷服務(wù)子程序中會訪問到的非自動變量(Non-automatic variables)
3). 多線程應(yīng)用中被幾個任務(wù)共享的變量
回答不出這個問題的人是不會被雇傭的。我認(rèn)為這是區(qū)分C程序員和嵌入式系統(tǒng)程序員的最基本的問題。嵌入式系統(tǒng)程序員經(jīng)常同硬件、中斷、RTOS等等打交道,所用這些都要求volatile變量。不懂得volatile內(nèi)容將會帶來災(zāi)難。
假設(shè)被面試者正確地回答了這是問題(嗯,懷疑這否會是這樣),我將稍微深究一下,看一下這家伙是不是直正懂得volatile完全的重要性。
1). 一個參數(shù)既可以是const還可以是volatile嗎?解釋為什么。
2). 一個指針可以是volatile 嗎?解釋為什么。
3). 下面的函數(shù)有什么錯誤:
int square(volatile int *ptr)
{
return *ptr * *ptr;
}
下面是答案:
1). 是的。一個例子是只讀的狀態(tài)寄存器。它是volatile因為它可能被意想不到地改變。它是const因為程序不應(yīng)該試圖去修改它。
2). 是的。盡管這并不很常見。一個例子是當(dāng)一個中服務(wù)子程序修該一個指向一個buffer的指針時。
3). 這段代碼的有個惡作劇。這段代碼的目的是用來返指針*ptr指向值的平方,但是,由于*ptr指向一個volatile型參數(shù),編譯器將產(chǎn)生類似下面的代碼:
int square(volatile int *ptr)
{
int a,b;
a = *ptr;
b = *ptr;
return a * b;
}
由于*ptr的值可能被意想不到地該變,因此a和b可能是不同的。結(jié)果,這段代碼可能返不是你所期望的平方值!正確的代碼如下:
long square(volatile int *ptr)
{
int a;
a = *ptr;
return a * a;
}
位操作(Bit manipulation)
9. 嵌入式系統(tǒng)總是要用戶對變量或寄存器進行位操作。給定一個整型變量a,寫兩段代碼,第一個設(shè)置a的bit 3,第二個清除a 的bit 3。在以上兩個操作中,要保持其它位不變。
對這個問題有三種基本的反應(yīng)
1). 不知道如何下手。該被面者從沒做過任何嵌入式系統(tǒng)的工作。
2). 用bit fields。Bit fields是被扔到C語言死角的東西,它保證你的代碼在不同編譯器之間是不可移植的,同時也保證了的你的代碼是不可重用的。我最近不幸看到Infineon為其較復(fù)雜的通信芯片寫的驅(qū)動程序,它用到了bit fields因此完全對我無用,因為我的編譯器用其它的方式來實現(xiàn)bit fields的。從道德講:永遠不要讓一個非嵌入式的家伙粘實際硬件的邊。
3). 用 #defines 和 bit masks 操作。這是一個有極高可移植性的方法,是應(yīng)該被用到的方法。最佳的解決方案如下:
#define BIT3 (0x1<<3)
static int a;
void set_bit3(void)
{
a |= BIT3;
}
void clear_bit3(void)
{
a &= ~BIT3;
}
一些人喜歡為設(shè)置和清除值而定義一個掩碼同時定義一些說明常數(shù),這也是可以接受的。我希望看到幾個要點:說明常數(shù)、|=和&=~操作。
訪問固定的內(nèi)存位置(Accessing fixed memory locations)
10. 嵌入式系統(tǒng)經(jīng)常具有要求程序員去訪問某特定的內(nèi)存位置的特點。在某工程中,要求設(shè)置一絕對地址為0x67a9的整型變量的值為0xaa66。編譯器是一個純粹的ANSI編譯器。寫代碼去完成這一任務(wù)。
這一問題測試你是否知道為了訪問一絕對地址把一個整型數(shù)強制轉(zhuǎn)換(typecast)為一指針是合法的。這一問題的實現(xiàn)方式隨著個人風(fēng)格不同而不同。典型的類似代碼如下:
int *ptr;
ptr = (int *)0x67a9;
*ptr = 0xaa55;
一個較晦澀的方法是:
*(int * const)(0x67a9) = 0xaa55;
即使你的品味更接近第二種方案,但我建議你在面試時使用第一種方案。
中斷(Interrupts)
11. 中斷是嵌入式系統(tǒng)中重要的組成部分,這導(dǎo)致了很多編譯開發(fā)商提供一種擴展—讓標(biāo)準(zhǔn)C支持中斷。具代表事實是,產(chǎn)生了一個新的關(guān)鍵字__interrupt。下面的代碼就使用了__interrupt關(guān)鍵字去定義了一個中斷服務(wù)子程序(ISR),請評論一下這段代碼的。
__interrupt double compute_area (double radius)
{
??? double area = PI * radius * radius;
??? printf(" Area = %f", area);
??? return area;
}
這個函數(shù)有太多的錯誤了,以至讓人不知從何說起了:
1). ISR 不能返回一個值。如果你不懂這個,那么你不會被雇用的。
2). ISR 不能傳遞參數(shù)。如果你沒有看到這一點,你被雇用的機會等同第一項。
3). 在許多的處理器/編譯器中,浮點一般都是不可重入的。有些處理器/編譯器需要讓額處的寄存器入棧,有些處理器/編譯器就是不允許在ISR中做浮點運算。此外,ISR應(yīng)該是短而有效率的,在ISR中做浮點運算是不明智的。
4). 與第三點一脈相承,printf()經(jīng)常有重入和性能上的問題。如果你丟掉了第三和第四點,我不會太為難你的。不用說,如果你能得到后兩點,那么你的被雇用前景越來越光明了。
代碼例子(Code examples)
12 . 下面的代碼輸出是什么,為什么?
void foo(void)
{
??? unsigned int a = 6;
??? int b = -20;
??? (a+b > 6) puts("> 6") : puts("<= 6");
}
這個問題測試你是否懂得C語言中的整數(shù)自動轉(zhuǎn)換原則,我發(fā)現(xiàn)有些開發(fā)者懂得極少這些東西。不管如何,這無符號整型問題的答案是輸出是“>6”。原因是當(dāng)表達式中存在有符號類型和無符號類型時所有的操作數(shù)都自動轉(zhuǎn)換為無符號類型。 因此-20變成了一個非常大的正整數(shù),所以該表達式計算出的結(jié)果大于6。這一點對于應(yīng)當(dāng)頻繁用到無符號數(shù)據(jù)類型的嵌入式系統(tǒng)來說是豐常重要的。如果你答錯了這個問題,你也就到了得不到這份工作的邊緣。
13. 評價下面的代碼片斷:
unsigned int zero = 0;
unsigned int compzero = 0xFFFF;
/*1's complement of zero */
對于一個int型不是16位的處理器為說,上面的代碼是不正確的。應(yīng)編寫如下:
unsigned int compzero = ~0;
這一問題真正能揭露出應(yīng)試者是否懂得處理器字長的重要性。在我的經(jīng)驗里,好的嵌入式程序員非常準(zhǔn)確地明白硬件的細(xì)節(jié)和它的局限,然而PC機程序往往把硬件作為一個無法避免的煩惱。
到了這個階段,應(yīng)試者或者完全垂頭喪氣了或者信心滿滿志在必得。如果顯然應(yīng)試者不是很好,那么這個測試就在這里結(jié)束了。但如果顯然應(yīng)試者做得不錯,那么我就扔出下面的追加問題,這些問題是比較難的,我想僅僅非常優(yōu)秀的應(yīng)試者能做得不錯。提出這些問題,我希望更多看到應(yīng)試者應(yīng)付問題的方法,而不是答案。不管如何,你就當(dāng)是這個娛樂吧…
動態(tài)內(nèi)存分配(Dynamic memory allocation)
14. 盡管不像非嵌入式計算機那么常見,嵌入式系統(tǒng)還是有從堆(heap)中動態(tài)分配內(nèi)存的過程的。那么嵌入式系統(tǒng)中,動態(tài)分配內(nèi)存可能發(fā)生的問題是什么?
這里,我期望應(yīng)試者能提到內(nèi)存碎片,碎片收集的問題,變量的持行時間等等。這個主題已經(jīng)在ESP雜志中被廣泛地討論過了(主要是 P.J. Plauger, 他的解釋遠遠超過我這里能提到的任何解釋),所有回過頭看一下這些雜志吧!讓應(yīng)試者進入一種虛假的安全感覺后,我拿出這么一個小節(jié)目:下面的代碼片段的輸出是什么,為什么?
char *ptr;
if ((ptr = (char *)malloc(0)) == NULL)
puts("Got a null pointer");
else
puts("Got a valid pointer");
這是一個有趣的問題。最近在我的一個同事不經(jīng)意把0值傳給了函數(shù)malloc,得到了一個合法的指針之后,我才想到這個問題。這就是上面的代碼,該代碼的輸出是“Got a valid pointer”。我用這個來開始討論這樣的一問題,看看被面試者是否想到庫例程這樣做是正確。得到正確的答案固然重要,但解決問題的方法和你做決定的基本原理更重要些。
Typedef
15. Typedef 在C語言中頻繁用以聲明一個已經(jīng)存在的數(shù)據(jù)類型的同義字。也可以用預(yù)處理器做類似的事。例如,思考一下下面的例子:
#define dPS struct s *
typedef struct s * tPS;
以上兩種情況的意圖都是要定義dPS 和 tPS 作為一個指向結(jié)構(gòu)s指針。哪種方法更好呢?(如果有的話)為什么?
這是一個非常微妙的問題,任何人答對這個問題(正當(dāng)?shù)脑颍┦菓?yīng)當(dāng)被恭喜的。答案是:typedef更好。思考下面的例子:
dPS p1,p2;
tPS p3,p4;
第一個擴展為
struct s * p1, p2;
上面的代碼定義p1為一個指向結(jié)構(gòu)的指,p2為一個實際的結(jié)構(gòu),這也許不是你想要的。第二個例子正確地定義了p3 和p4 兩個指針。
晦澀的語法
16. C語言同意一些令人震驚的結(jié)構(gòu),下面的結(jié)構(gòu)是合法的嗎,如果是它做些什么?
int a = 5, b = 7, c;
c = a+++b;
這個問題將做為這個測驗的一個愉快的結(jié)尾。不管你相不相信,上面的例子是完全合乎語法的。問題是編譯器如何處理它?水平不高的編譯作者實際上會爭論這個問題,根據(jù)最處理原則,編譯器應(yīng)當(dāng)能處理盡可能所有合法的用法。因此,上面的代碼被處理成:
c = a++ + b;
因此, 這段代碼持行后a = 6, b = 7, c = 12。
如果你知道答案,或猜出正確答案,做得好。如果你不知道答案,我也不把這個當(dāng)作問題。我發(fā)現(xiàn)這個問題的最大好處是:這是一個關(guān)于代碼編寫風(fēng)格,代碼的可讀性,代碼的可修改性的好的話題
對初學(xué)者而言,匯編的許多命令太復(fù)雜,往往學(xué)習(xí)很長時間也寫不出一個漂漂亮亮的程序,以致妨礙了我們學(xué)習(xí)匯編的興趣,不少人就此放棄。所以我個人看法學(xué)匯編,不一定要寫程序,寫程序確實不是匯編的強項,大家不妨玩玩DEBUG,有時CRACK出一個小軟件比完成一個程序更有成就感(就像學(xué)電腦先玩游戲一樣)。某些高深的指令事實上只對有經(jīng)驗的匯編程序員有用,對我們而言,太過高深了。為了使學(xué)習(xí)匯編語言有個好的開始,你必須要先排除那些華麗復(fù)雜的命令,將注意力集中在最重要的幾個指令上(CMP LOOP MOV JNZ……)。但是想在啰里吧嗦的教科書中完成上述目標(biāo),談何容易,所以本人整理了這篇超濃縮(用WINZIP、WINRAR…依次壓迫,嘿嘿!)教程。大言不慚的說,看通本文,你完全可以“不經(jīng)意”間在前輩或是后生賣弄一下DEBUG,很有成就感的,試試看!那么――這個接下來呢?―― Here we go!(閱讀時看不懂不要緊,下文必有分解)
因為匯編是通過CPU和內(nèi)存跟硬件對話的,所以我們不得不先了解一下CPU和內(nèi)存:(關(guān)于數(shù)的進制問題在此不提)
CPU是可以執(zhí)行電腦所有算術(shù)╱邏輯運算與基本 I/O 控制功能的一塊芯片。一種匯編語言只能用于特定的CPU。也就是說,不同的CPU其匯編語言的指令語法亦不相同。個人電腦由1981年推出至今,其CPU發(fā)展過程為:8086→80286→80386→80486→PENTIUM →……,還有AMD、CYRIX等旁支。后面兼容前面CPU的功能,只不過多了些指令(如多能奔騰的MMX指令集)、增大了寄存器(如386的32位EAX)、增多了寄存器(如486的FS)。為確保匯編程序可以適用于各種機型,所以推薦使用8086匯編語言,其兼容性最佳。本文所提均為8086匯編語言。寄存器(Register)是CPU內(nèi)部的元件,所以在寄存器之間的數(shù)據(jù)傳送非常快。用途:1.可將寄存器內(nèi)的數(shù)據(jù)執(zhí)行算術(shù)及邏輯運算。2.存于寄存器內(nèi)的地址可用來指向內(nèi)存的某個位置,即尋址。3.可以用來讀寫數(shù)據(jù)到電腦的周邊設(shè)備。8086 有8個8位數(shù)據(jù)寄存器,這些8位寄存器可分別組成16位寄存器:AH&AL=AX:累加寄存器,常用于運算;BH&BL=BX:基址寄存器,常用于地址索引;CH&CL=CX:計數(shù)寄存器,常用于計數(shù);DH&DL=DX:數(shù)據(jù)寄存器,常用于數(shù)據(jù)傳遞。為了運用所有的內(nèi)存空間,8086設(shè)定了四個段寄存器,專門用來保存段地址:CS(Code Segment):代碼段寄存器;DS(Data Segment):數(shù)據(jù)段寄存器;SS(Stack Segment):堆棧段寄存器;ES(Extra Segment):附加段寄存器。當(dāng)一個程序要執(zhí)行時,就要決定程序代碼、數(shù)據(jù)和堆棧各要用到內(nèi)存的哪些位置,通過設(shè)定段寄存器 CS,DS,SS 來指向這些起始位置。通常是將DS固定,而根據(jù)需要修改CS。所以,程序可以在可尋址空間小于64K的情況下被寫成任意大小。 所以,程序和其數(shù)據(jù)組合起來的大小,限制在DS 所指的64K內(nèi),這就是COM文件不得大于64K的原因。8086以內(nèi)存做為戰(zhàn)場,用寄存器做為軍事基地,以加速工作。除了前面所提的寄存器外,還有一些特殊功能的寄存器:IP(Intruction Pointer):指令指針寄存器,與CS配合使用,可跟蹤程序的執(zhí)行過程;SP(Stack Pointer):堆棧指針,與SS配合使用,可指向目前的堆棧位置。BP(Base Pointer):基址指針寄存器,可用作SS的一個相對基址位置;SI(Source Index):源變址寄存器可用來存放相對于DS段之源變址指針;DI(Destination Index):目的變址寄存器,可用來存放相對于 ES 段之目的變址指針。還有一個標(biāo)志寄存器FR(Flag Register),有九個有意義的標(biāo)志,將在下文用到時詳細(xì)說明。
內(nèi)存是電腦運作中的關(guān)鍵部分,也是電腦在工作中儲存信息的地方。內(nèi)存組織有許多可存放數(shù)值的儲存位置,叫“地址”。8086地址總線有20位,所以CPU擁有達1M的尋址空間,這也是DOS的有效控制范圍,而8086能做的運算僅限于處理16位數(shù)據(jù),即只有0到64K,所以,必須用分段尋址才能控制整個內(nèi)存地址。完整的20位地址可分成兩部份:1.段基址(Segment):16位二進制數(shù)后面加上四個二進制0,即一個16進制0,變成20位二進制數(shù),可設(shè)定1M中任何一個64K段,通常記做16位二進制數(shù);2.偏移量(Offset):直接使用16位二進制數(shù),指向段基址中的任何一個地址。如:2222(段基址):3333(偏移量),其實際的20位地址值為:25553。除了上述營養(yǎng)要充分吸收外,你還要知道什么是DOS、BIOS功能調(diào)用,簡單的說,功能調(diào)用類似于WIN95 API,相當(dāng)于子程序。匯編寫程序已經(jīng)夠要命了,如果不用MS、IBM的子程序,這日子真是沒法過了(關(guān)于功能調(diào)用詳見《電腦愛好者》98年11期)。
編寫匯編語言有兩種主要的方法:1.使用MASM或TASM等編譯器;2.使用除錯程序DEBUG.COM。DEBUG其實并不能算是一個編譯器,它的主要用途在于除錯,即修正匯編程序中的錯誤。不過,也可以用來寫短的匯編程序,尤其對初學(xué)者而言,DEBUG 更是最佳的入門工具。因為DEBUG操作容易:只要鍵入DEBUG回車,A回車即可進行匯編,過程簡單,而使用編譯器時,必須用到文本編輯器、編譯器本身、LINK以及EXE2BIN等程序,其中每一個程序都必須用到一系列相當(dāng)復(fù)雜的命令才能工作,而且用編譯器處理源程序,必須加入許多與指令語句無關(guān)的指示性語句,以供編譯器識別,使用 DEBUG 可以避免一開始就碰到許多難以理解的程序行。DEBUG 除了能夠匯編程序之外,還可用來檢查和修改內(nèi)存位置、載入儲存和執(zhí)行程序、以及檢查和修改寄存器,換句話說,DEBUG是為了讓我們接觸硬件而設(shè)計的。(8086常用指令用法將在每個匯編程序中講解,限于篇幅,不可能將所有指令列出)。
DEBUG的的A命令可以匯編出簡單的COM文件,所以DEBUG編寫的程序一定要由地址 100h(COM文件要求)開始才合法。FOLLOW ME,SETP BY SETP(步步回車):
輸入 A100 ; 從DS:100開始匯編
2.輸入 MOV DL,1 ; 將數(shù)值 01h 裝入 DL 寄存器
3.輸入 MOV AH,2 ; 將數(shù)值 02h 裝入 DL 寄存器
4.輸入 INT 21 ; 調(diào)用DOS 21號中斷2號功能,用來逐個顯示裝入DL的字符
5.輸入 INT 20 ; 調(diào)用DOS 20號中斷,終止程序,將控制權(quán)交回給 DEBUG
6.請按 Enter 鍵
7.現(xiàn)在已將匯編語言程序放入內(nèi)存中了,輸入 G(運行)
8.出現(xiàn)結(jié)果:輸出一個符號。
ㄖ ←輸出結(jié)果其實不是它,因WORD97無法顯示原結(jié)果,故找一贗品將就著。
Program terminated normally
我們可以用U命令將十六進制的機器碼反匯編(Unassemble)成匯編指令。你將發(fā)現(xiàn)每一行右邊的匯編指令就是被匯編成相應(yīng)的機器碼,而8086實際上就是以機器碼來執(zhí)行程序。
1.輸入 U100,106
1FED:0100 B201 MOV DL,01
1FED:0102 B402 MOV AH,02
1FED:0104 CD21 INT 21
1FED:0106 CD20 INT 20
DEBUG可以用R命令來查看、改變寄存器內(nèi)容。CS:IP寄存器,保存了將執(zhí)行指令地址。
1.輸入R
AX=0000 BX=0000 CX=0000 DX=0000 SP=FFEE BP=0000 SI=0000 DI=0000
DS=1FED ES=1FED SS=1FED CS=1FED IP=0100 NV UP EI PL NZ NA PO NC
1FED:0100 B201 MOV DL,01
當(dāng)程序由DS:100開始執(zhí)行,那么終止程序時,DEBUG會自動將IP內(nèi)容重新設(shè)定為100。當(dāng)你要將此程序做成一個獨立的可執(zhí)行文件,則可以用N命令對該程序命名。但一定要為COM文件,否則無法以DEBUG載入。
輸入N SMILE.COM ;我們得告訴DEBUG程序長度:程序從100開始到106,故占用7
;字節(jié)。我們利用BX存放長度值高位部分,而以CX存放低位部分。
2.輸入RBX ;查看 BX 寄存器的內(nèi)容,本程序只有7個字節(jié),故本步可省略
3.輸入 RCX ;查看 CX 寄存器的內(nèi)容
4.輸入 7 ;程序的字節(jié)數(shù)
5.輸入 W ;用W命令將該程序?qū)懭耄╓rite)磁盤中
修行至此,我們便可以真正接觸8086匯編指令了。 當(dāng)我們寫匯編語言程序的時候,通常不會直接將機器碼放入內(nèi)存中,而是打入一串助記符號(Mnemonic Symbols),這些符號比十六進制機器碼更容易記住,此之謂匯編指令。助記符號,告訴CPU應(yīng)執(zhí)行何種運算。 也就是說,助憶符號所構(gòu)成的匯編語言是為人設(shè)計的,而機器語言是對PC設(shè)計的。
現(xiàn)在,我們再來剖析一個可以將所有ASCII碼顯示出來的程序。
1. 輸入 DEBUG
2. 輸入 A100
3.輸入 MOV CX,0100 ;裝入循環(huán)次數(shù)
MOV DL,00 ;裝入第一個ASCII碼,隨后每次循環(huán)裝入新碼
MOV AH,02
INT 21
INC DL ;INC:遞增指令,每次將數(shù)據(jù)寄存器 DL 內(nèi)的數(shù)值加 1
LOOP 0105 ;LOOP:循環(huán)指令,每執(zhí)行一次LOOP,CX值減1,并跳
;到循環(huán)的起始地址105,直到CX為0,循環(huán)停止
INT 20
4.輸入 G即可顯示所有ASCII碼
當(dāng)我們想任意顯示字符串,如:UNDERSTAND?,則可以使用DOS21H號中斷9H號功能。輸入下行程序,存盤并執(zhí)行看看:
1.輸入 A100
MOV DX,109 ;DS:DX = 字符串的起始地址
MOV AH,9 ;DOS的09h功能調(diào)用
INT 21 ;字符串輸出
INT 20
DB 'UNDERSTAND?$';定義字符串
在匯編語言中,有兩種不同的指令:1.正規(guī)指令:如 MOV 等,是屬于CPU的指令,用來告訴CPU在程序執(zhí)行時應(yīng)做些什么,所以它會以運算碼(OP-code)的方式存入內(nèi)存中;2.偽指令:如DB等,是屬于DEBUG等編譯器的指令,用來告訴編譯器在編譯時應(yīng)做些什么。DB(Define Byte)指令用來告訴DEBUG 將單引號內(nèi)的所有ASCII 碼放入內(nèi)存中。使用 9H 功能的字符串必須以$結(jié)尾。用D命令可用來查看DB偽指令將那些內(nèi)容放入內(nèi)存。
6.輸入 D100
1975:0100 BA 09 01 B4 09 CD 21 CD-20 75 6E 64 65 72 73 74 ......!. underst
1975:0110 61 6E 64 24 8B 46 F8 89-45 04 8B 46 34 00 64 19 and$.F..E..F4.d.
1975:0120 89 45 02 33 C0 5E 5F C9-C3 00 C8 04 00 00 57 56 .E.3.^_.......WV
1975:0130 6B F8 0E 81 C7 FE 53 8B-DF 8B C2 E8 32 FE 0B C0 k.....S.....2...
1975:0140 74 05 33 C0 99 EB 17 8B-45 0C E8 D4 97 8B F0 89 t.3.....E.......
1975:0150 56 FE 0B D0 74 EC 8B 45-08 03 C6 8B 56 FE 5E 5F V...t..E....V.^_
1975:0160 C9 C3 C8 02 00 00 6B D8-0E 81 C3 FE 53 89 5E FE ......k.....S.^.
1975:0170 8B C2 E8 FB FD 0B C0 75-09 8B 5E FE 8B 47 0C E8 .......u..^..G..
現(xiàn)在,我們來剖析另一個程序:由鍵盤輸入任意字符串,然后顯示出來。db 20指示DEBUG保留20h個未用的內(nèi)存空間供緩沖區(qū)使用。
輸入A100
MOV DX,0116 ;DS:DX = 緩沖區(qū)地址,由DB偽指令確定緩沖區(qū)地址
MOV AH,0A ;0Ah 號功能調(diào)用
INT 21 ;鍵盤輸入緩沖區(qū)
MOV DL,0A ;由于功能Ah在每個字符串最后加一個歸位碼(0Dh由 Enter
MOV AH,02 ;產(chǎn)生),使光標(biāo)自動回到輸入行的最前端,為了使新輸出的
INT 21 ;字符串不會蓋掉原來輸入的字符串,所以利用功能2h加一
;個換行碼(OAh),使得光標(biāo)移到下一行的的最前端。
MOV DX,0118 ;裝入字符串的起始位置
MOV AH,09 ;9h功能遇到$符號才會停止輸出,故字符串最后必須加上
INT 21 ;$,否則9h功能會繼續(xù)將內(nèi)存中的無用數(shù)據(jù)胡亂顯示出來
INT 20
DB 20 ;定義緩沖區(qū)
送你一句話:學(xué)匯編切忌心浮氣燥。
客套話就不講了。工欲善其事,必先利其器。與其說DEBUG 是編譯器,倒不如說它是“直譯器”,DEBUG的A命令只可將一行匯編指令轉(zhuǎn)成機器語言,且立刻執(zhí)行。真正編譯器(MASM)的運作是利用文本編輯器(EDIT等)將匯編指令建成一個獨立且附加名為.ASM的文本文件,稱源程序。它是MASM 程序的輸入部分。MASM將輸入的ASM文件,編譯成.OBJ文件,稱為目標(biāo)程序。OBJ文件僅包含有關(guān)程序各部份要載入何處及如何與其他程序合并的信息,無法直接載入內(nèi)存執(zhí)行。鏈結(jié)程序LINK則可將OBJ文件轉(zhuǎn)換成可載入內(nèi)存執(zhí)行(EXEcute)的EXE文件。還可以用EXE2BIN,將符合條件的EXE文件轉(zhuǎn)成COM文件(COM 文件不但占用的內(nèi)存最少,而且運行速度最快)。
下面我們用MASM寫一個與用DEBUG寫的第一個程序功能一樣的程序。
用EDIT編輯一個SMILE.ASM的源程序文件。
源程序 DEBUG 程序
prognam segment
assume cs:prognam
org 100h A100
mov dl,1 mov dl,1
mov ah,2 mov ah,2
int 21h int 21
int 20h int 20
prognam ends
end
比較一下:1.因為MASM會將所有的數(shù)值假設(shè)為十進制,而DEBUG則只使用十六進制,所以在源程序中,我們必須在有關(guān)數(shù)字后加上代表進制的字母,如H代表十六進制,D代表十進制。若是以字母開頭的十六進制數(shù)字,還必須在字母前加個0,以表示它是數(shù),如0AH。2.源程序增加五行敘述:prognam segment 與 prognam ends 是成對的,用來告訴 MASM 及LINK,此程序?qū)⒎旁谝粋€稱為PROGNAM(PROGram NAMe)的程序段內(nèi),其中段名(PROGNAM)可以任取,但其位置必須固定。assume cs:prognam 必須在程序的開頭,用來告訴編譯器此程序所在段的位置放在CS寄存器中。end用來告訴MASM,程序到此結(jié)束, ORG 100H作用相當(dāng)于DEBUG的A100,從偏移量100開始匯編。COM 文件的所有源程序都必須包含這五行,且必須依相同的次序及位置出現(xiàn),這點東西記下就行,千篇一律。接著,我們用MASM編譯SMILE.ASM。
輸入 MASM SMILE ←不用打入附加名.ASM。
Microsoft (R) Macro Assembler Version 5.10
Copyright (C) Microsoft Corp 1981, 1988. All rights reserved.
Object filename [SMILE.OBJ]: ←是否改動輸出OBJ文件名,如不改就ENTER
Source listing [NUL.LST]: ← 是否需要列表文件(LST),不需要就ENTER
Cross-reference [NUL.CRF]: ←是否需要對照文件(CRF),不需要則ENTER
50162 + 403867 Bytes symbol space free
0 Warning Errors ←警告錯誤,表示編譯器對某些語句不理解,通常是輸入錯誤。
0 Severe Errors ←嚴(yán)重錯誤,會造成程序無法執(zhí)行,通常是語法結(jié)構(gòu)錯誤。
如果沒有一個錯誤存在,即可生成OBJ文件。OBJ中包含的是編譯后的二進制結(jié)果,它還無法被 DOS載入內(nèi)存中加以執(zhí)行,必須加以鏈結(jié)(Linking)。以LINK將OBJ文件(SMILE.OBJ)鏈結(jié)成 EXE 文件(SMILE.EXE)時,。
1.輸入 LINK SMILE ←不用附加名OBJ
Microsoft (R) Overlay Linker Version 3.64
Copyright (C) Microsoft Corp 1981, 1988. All rights reserved.
Run File [SMILE.EXE]: ← 是否改動輸出EXE文件名,如不改就ENTER
List File [NUL.MAP]: ← 是否需要列表文件(MAP),不需要則ENTER
Libraries [.LIB]: ←是否需要庫文件,要就鍵入文件名,不要則ENTER
LINK : warning L4021: no stack segment← 由于COM文件不使用堆棧段,所以錯誤信息
←"no stack segment"并不影響程序正常執(zhí)行
至此已經(jīng)生成EXE文件,我們還須使用EXE2BIN 將EXE文件(SMILE.EXE),轉(zhuǎn)換成COM文件(SMILE.COM)。輸入EXE2BIN SMILE產(chǎn)生 BIN 文件(SMILE.BIN)。其實 BIN 文件與 COM 文件是完全相同的,但由于DOS只認(rèn)COM、EXE及BAT文件,所以BIN文件無法被正確執(zhí)行,改名或直接輸入 EXE2BIN SMILE SMILE.COM即可。現(xiàn)在,磁盤上應(yīng)該有 SMILE.COM 文件了,你只要在提示符號C:>下,直接輸入文件名稱 SMILE ,就可以執(zhí)行這個程序了。
你是否覺得用編譯器產(chǎn)生程序的方法,比 DEBUG 麻煩多了!以小程序而言,的確是如此,但對于較大的程序,你就會發(fā)現(xiàn)其優(yōu)點了。我們再將ASCII程序以編譯器方式再做一次,看看有無差異。首先,用EDIT.COM建立 ASCII.ASM 文件。
prognam segment ;定義段
assume cs:prognam ;把上面定義段的段基址放入 CS
mov cx,100h ; 裝入循環(huán)次數(shù)
mov dl,0 ; 裝入第一個ASCII碼,隨后每次循環(huán)裝入新碼
next: mov ah,2
int 21h
inc dl ;INC:遞增指令,每次將數(shù)據(jù)寄存器 DL 內(nèi)的數(shù)值加 1
loop next ; 循環(huán)指令,執(zhí)行一次,CX減1,直到CX為0,循環(huán)停止
int 20h
prognam ends ;段終止
end ;匯編終止
在匯編語言的源程序中,每一個程序行都包含三項元素:
start: mov dl,1 ;裝入第一個ASCII碼,隨后每次循環(huán)裝入新碼
標(biāo)識符 表達式 注解
在原始文件中加上注解可使程序更易理解,便于以后參考。每行注解以“;”與程序行分離。編譯器對注解不予理會,注解的數(shù)據(jù)不會出現(xiàn)在OBJ、EXE或COM文件中。由于我們在寫源程序時,并不知道每一程序行的地址,所以必須以符號名稱來代表相對地址,稱為“標(biāo)識符”。我們通常在適當(dāng)行的適當(dāng)位置上,鍵入標(biāo)識符。標(biāo)識符(label)最長可達31 個字節(jié),因此我們在程序中,盡量以簡潔的文字做為標(biāo)識符。現(xiàn)在,你可以將此ASCII.ASM 文件編譯成 ASCII.COM 了。1.MASM ASCII,2.LINK ASCII,3.EXE2BIN ASCII ASCII.COM。
注意:當(dāng)你以編譯器匯編你設(shè)計的程序時,常會發(fā)生打字錯誤、標(biāo)識符名稱拼錯、十六進制數(shù)少了h、邏輯錯誤等。匯編老手常給新人的忠告是:最好料到自己所寫的程序一定會有些錯誤(別人告訴我的);如果第一次執(zhí)行程序后,就得到期望的結(jié)果,你最好還是在檢查一遍,因為它可能是錯的。原則上,只要大體的邏輯架構(gòu)正確,查找程序中錯誤的過程,與寫程序本身相比甚至更有意思。寫大程序時,最好能分成許多模塊,如此可使程序本身的目的較單純,易于撰寫與查錯,另外也可讓程序中不同部份之間的界限較清楚,節(jié)省編譯的時間。如果讀程序有讀不懂的地方最好用紙筆記下有關(guān)寄存器、內(nèi)存等內(nèi)容,在紙上慢慢比劃,就豁然開朗了。 下面我們將寫一個能從鍵盤取得一個十進制的數(shù)值,并將其轉(zhuǎn)換成十六進制數(shù)值而顯示于屏幕上的“大程序”。前言:要讓8086執(zhí)行這樣的功能,我們必須先將此問題分解成一連串的步驟,稱為程序規(guī)劃。首先,以流程圖的方式,來確保整個程序在邏輯上沒有問題(不用說了吧!什么語言都要有此步驟)。這種模塊化的規(guī)劃方式,稱之為“由上而下的程序規(guī)劃”。而在真正寫程序時,卻是從最小的單位模塊(子程序)開始,當(dāng)每個模塊都完成之后,再合并成大程序;這種大處著眼,小處著手的方式稱為“由下而上的程序設(shè)計”。
我們的第一個模塊是BINIHEX,其主要用途是從8086的BX寄存器中取出二進制數(shù),并以十六進制方式顯示在屏幕上。注意:子程序如不能獨立運行,實屬正常。
binihex segment
assume cs:binihex
mov ch,4 ;記錄轉(zhuǎn)換后的十六進制位數(shù)(四位)
rotate: mov cl,4 ;利用CL當(dāng)計數(shù)器,記錄寄存器數(shù)位移動次數(shù)
rol bx,cl ;循環(huán)寄存器BX的內(nèi)容,以便依序處理4個十六進制數(shù)
mov al,bl ;把bx低八位bl內(nèi)數(shù)據(jù)轉(zhuǎn)移至al
and al,0fh ;把無用位清零
add al,30h ;把AL內(nèi)數(shù)據(jù)加30H,并存入al
cmp al,3ah ;與3ah比較
jl printit ;小于3ah則轉(zhuǎn)移
add al,7h ;把AL內(nèi)數(shù)據(jù)加30H,并存入al
printit:mov dl,al ;把ASCII碼裝入DL
mov ah,2
int 21h
dec ch ;ch減一,減到零時,零標(biāo)志置1
jnz rotate ;JNZ:當(dāng)零標(biāo)志未置1,則跳到指定地址。即:不等,則轉(zhuǎn)移
int 20h ;從子程序退回主程序
binihex ends
end
利用循環(huán)左移指令ROL循環(huán)寄存器BX(BX內(nèi)容將由第二個子程序提供)的內(nèi)容,以便依序處理4個十六進制數(shù):1. 利用CL當(dāng)計數(shù)器,記錄寄存器移位的次數(shù)。2.將BX的第一個十六進制值移到最右邊。利用 AND (邏輯“與”運算:對應(yīng)位都為1時,其結(jié)果為1,其余情況為零)把不要的部份清零,得到結(jié)果:先將BL值存入AL中,再利用AND以0Fh(00001111)將AL的左邊四位清零。由于0到9的ASCII碼為30h到39h,而A到F之ASCII碼為41h到46h,間斷了7h,所以得到結(jié)果:若AL之內(nèi)容小于3Ah,則AL值只加30h,否則AL再加7h。ADD指令會將兩個表達式相加,其結(jié)果存于左邊表達式內(nèi)。標(biāo)志寄存器(Flag Register)是一個單獨的十六位寄存器,有9個標(biāo)志位,某些匯編指令(大部份是涉及比較、算術(shù)或邏輯運算的指令)執(zhí)行時,會將相關(guān)標(biāo)志位置1或清0, 常碰到的標(biāo)志位有零標(biāo)志(ZF)、符號標(biāo)志(SF)、溢出標(biāo)志(OF)和進位標(biāo)志(CF)。 標(biāo)志位保存了某個指令執(zhí)行后對它的影響,可用其他相關(guān)指令,查出標(biāo)志的狀態(tài),根據(jù)狀態(tài)產(chǎn)生動作。CMP指令很像減法,是將兩個表達式的值相減,但寄存器或內(nèi)存的內(nèi)容并未改變,只是相對的標(biāo)志位發(fā)生改變而已:若 AL 值小于 3Ah,則正負(fù)號標(biāo)志位會置0,反之則置1。 JL指令可解釋為:小于就轉(zhuǎn)移到指定位置,大于、等于則向下執(zhí)行。CMP和JG 、JL等條件轉(zhuǎn)移指令一起使用,可以形成程序的分支結(jié)構(gòu),是寫匯編程序常用技巧。
第二個模塊DECIBIN 用來接收鍵盤打入的十進制數(shù),并將它轉(zhuǎn)換成二進制數(shù)放于BX 寄存器中,供模塊1 BINIHEX使用。
decibin segment
assume cs:decibin
mov bx,0 ;BX清零
newchar:mov ah,1 ;
int 21h ;讀一個鍵盤輸入符號入al,并顯示
sub al,30h ;al減去30H,結(jié)果存于al中,完成ASCII碼轉(zhuǎn)二進制碼
jl exit ;小于零則轉(zhuǎn)移
cmp al,9d
jg exit ;左>右則轉(zhuǎn)移
cbw ;8位al轉(zhuǎn)換成16位ax
xchg ax,bx ;互換ax和bx內(nèi)數(shù)據(jù)
mov cx,10d ;十進制數(shù)10入cx
mul cx ;表達式的值與ax內(nèi)容相乘,并將結(jié)果存于ax
xchg ax,bx
add bx,ax
jmp newchar ;無條件轉(zhuǎn)移
exit: int 20 ;回主程序
decibin ends
end
CBW 實際結(jié)果是:若AL中的值為正,則AH填入00h;反之,則AH填入FFh。XCHG常用于需要暫時保留某個寄存器中的內(nèi)容時。
當(dāng)然,還得一個子程序(CRLF)使后顯示的十六進制數(shù)不會蓋掉先輸入的十進制數(shù)。
crlf segment
assume cs:crlf
mov dl,0dh ;回車的ASCII碼0DH入DL
mov ah,2
int 21h
mov dl,0ah ;換行的ASSII碼0AH入AH
mov ah,2
int 21h
int 20 ;回主程序
crlf ends
end
現(xiàn)在我們就可以將BINIHEX、DECIBIN及CRLF等模塊合并成一個大程序了。首先,我們要將這三個模塊子程序略加改動。然后,再寫一段程序來調(diào)用每一個子程序。
crlf proc near;
mov dl,0dh
mov ah,2
int 21h
mov dl,0ah
mov ah,2
int 21h
ret
crlf endp
類似SEGMENT與ENDS的偽指令,PROC與ENDP也是成對出現(xiàn),用來識別并定義一個程序。其實,PROC 真正的作用只是告訴編譯器:所調(diào)用的程序是屬于近程(NEAR)或遠程(FAR)。 一般的程序是由 DEBUG 直接調(diào)用的,所以用 INT 20 返回,用 CALL 指令所調(diào)用的程序則改用返回指令RET,RET會把控制權(quán)轉(zhuǎn)移到棧頂所指的地址,而該地址是由調(diào)用此程序的 CALL指令所放入的。
各模塊都搞定了,然后我們把子程序組合起來就大功告成
decihex segment ;主程序
assume cs:decihex
org 100h
mov cx,4 ;循環(huán)次數(shù)入cx;由于子程序要用到cx,故子程序要將cx入棧
repeat: call decibin;調(diào)用十進制轉(zhuǎn)二進制子程序
call crlf ;調(diào)用添加回、換行符子程序
call binihex ;調(diào)用二進制轉(zhuǎn)十六進制并顯示子程序
call crlf
loop repeat ;循環(huán)4次,可連續(xù)運算4次
mov ah,4ch ; 調(diào)用DOS21號中斷4c號功能,退出程序,作用跟INT 20H
int 21H ; 一樣,但適用面更廣,INT20H退不出時,試一下它
decibin proc near push cx ;將cx壓入堆棧,;
┇ exit: pop cx ;將cx還原; retdecibin endp binihex proc near push cx
┇ pop cx retbinihex endp crlf proc near
push cx
┇ pop cx retcrlf endpdecihex ends end
CALL指令用來調(diào)用子程序,并將控制權(quán)轉(zhuǎn)移到子程序地址,同時將CALL的下行一指令地址定為返回地址,并壓入堆棧中。CALL 可分為近程(NEAR)及遠程(FAR)兩種:1.NEAR:IP的內(nèi)容被壓入堆棧中,用于程序與程序在同一段中。2.FAR:CS 、IP寄存器的內(nèi)容依次壓入堆棧中,用于程序與程序在不同段中。PUSH、POP又是一對指令用于將寄存器內(nèi)容壓入、彈出,用來保護寄存器數(shù)據(jù),子程序調(diào)用中運用較多。堆棧指針有個“后進先出”原則,像PUSH AX,PUSH BX…POP BX,POP AX這樣才能作到保護數(shù)據(jù)絲毫不差。
匯編語言超濃縮教程到這要告一段落了,希望能奠定你獨立設(shè)計的基礎(chǔ)。而更多更好的技巧則全依賴你平時的積累了。祝你成功!
??? 在學(xué)匯編時,很多初學(xué)者對PC的尋址方式和很不理解,甚至是很難理解。的確,這方面的知識是很抽象的,需要比較強的空間想象能力。尤其是我們在輸入字符串時,那這些字符是如何進行排列的呢?對于,這個問題,我相信很多初學(xué)者也是很難想象是如何排列。但是,我可以這樣比喻:內(nèi)存就是有很多棟“樓房”,“樓房”又是由“單元號”,“門戶號”組成,那“樓房”就相當(dāng)于內(nèi)存地址的段地址,“單元號”就相當(dāng)于內(nèi)存的的 偏移地址,“門戶號(家)”就相當(dāng)于“變地址”,而每個單元有16個"門戶號(家)",又當(dāng)我們找到"門戶號(家)"后,走進這個"門戶號(家)"就會見到里面會有"人",而我們所說的人就是寄存器所指的"內(nèi)容"了,我畫個圖給你們看就會一目了然了。
用DEBUG的D命令得出這樣的效果:
|---------->0B1F就是"樓房"------>段地址
|
|???? |------>右邊的就是"單元號"--->偏移地址
|???? |
|???? |??????????? |-------->這部分就是"門戶號"----->變地址
|???? |????????? |<------------------------------------------>|
0B1F:0100 00 80 FF 02 75 05 C6 46-00 00 C3 E8 8C EB B4 3B
0B1F:0110 CD 21 72 39 8B FA 33 C0-8B C8 49 26 34 00 0E 0B
'
'
'
[省略]
看完這個圖之后,是不是就很明了呢?但是聰明的人就會有疑問,那我們怎么走進"門戶號(家)"呢?問得好,所以了為了可以走進"門戶號(家)",就出現(xiàn)了一個叫做"尋址方式"的概念!說白了,就是教你如何找到這個"門戶號(家)".呵呵!
好現(xiàn)在都明白了嗎?那你們就看看我是怎么理解PC的尋址方式(通俗易懂):
在這我就只介紹比較難理解的:
1:寄存器直接尋址:
你就想成:其實你已經(jīng)站在你要找的"門戶號(家)"面前了,直接敲門進去就OK了!
例子: MOV AX,[2000H]
MOV AX,2000H -->2000H為存放操作數(shù)單元號的符號地址
上面兩者是不等效的
2:寄存器間接尋址方式:
你就想成:你已經(jīng)站在你要找的"門戶號(家)"的"單元號",你要找到它,必須知道它在當(dāng)前"單元號"幾樓.假如它在6樓,那你就上到6樓就OK了!!注意,最高只有16樓,因為什么呢?那就用DEBUG的D命令看看呀,慢慢數(shù)哦,呵呵!!
例子: MOV AX,[BX]
計算公式: 物理地址=16d*(DS)+(BX)
物理地址=16d*(DS)+(SI)
物理地址=16d*(DS)+(DI)
物理地址=16d*(SS)+(BP)
3:寄存器相對尋址方式:
你就想成:你要找的"門戶號(家)"其實就在你家的樓上或者樓下,你要找到它,就 必須知道它在你樓上幾樓,或者在樓下幾樓!就OK了!
例子: MOV AX,COUNT[SI]
MOV AX,[COUNT+SI]
其中 COUNT為位移量的符號地址
計算公式: 物理地址=16d*(DS)+(BX)+8位位移量
或+(SI) 或 16位位偏移量
或+(DI)
物理地址=16d*(SS)+(BP)+8位偏移量
4:基址變址尋址方式:
你就想成:你要找的"門戶號(家)"是跟住在同一棟樓的不同"單元號",你要找到它,就必須知道它是該棟的哪個"單元號",并且住在幾樓!那樣你就可以找到它了 !
例子: MOV AX,[BX][DI]
MOV AX,[BX+DI]
計算公式: 物理地址=16d*(DS)+(BX)+(SI)
或+(DI)
物理地址=16d*(SS)+(BP)+(SI)
或+(DI)
5:相對基址變址尋址方式:
你就想成:你就想成:你要找的"門戶號(家)"是跟住在同一棟樓的不同"單元號",它比你高幾層樓或者低幾層樓,然后用的你目前的樓數(shù)+/-就可以得出你要找的住在幾樓了!
例子: MOV,AX,MASK[BX][SI]
MOV,AX,MASK[BX+SI]
MOV,AX,[MASK+BX+SI]
以上三個例子是等效的!!
計算公式: 物理地址=16d*(DS)+(BX)+(SI)+8位位移量
或+(DI) 或 16位位偏移量
物理地址=16d*(SS)+(BP)+(SI)+8位位移量
或+(DI) 或 16位位偏移量
---------------------------------------------------------------------
呵呵,終于寫完了這篇教程,好累哦!! 是不是覺得我的思維很另類呀,要創(chuàng)新呀!
書上太理論了,我就創(chuàng)新一個,不知道你們看得懂嗎?
呵呵,反正你們不要!@##)(#$*!@(@我就行了,我很努力寫了!!!
下面,我舉個程序例子,讓你們加深印象!!!
----------------------------------------------------------------------
編程步驟:
1: 建立緩沖區(qū),為輸入字符串(最多能輸入9個)
2: 取緩沖區(qū)的首地址,以便后面進行"寄存器間接尋址方式"
3: 利用"寄存器間接尋址方式"取得實際輸入字符個數(shù),以便確認(rèn)循環(huán)次數(shù)
4: 利用"寄存器間接尋址方式"輸入字符串的最后一個字符
5: 利用LOOP指令和2號顯示功能來進行倒著顯示
----------------------------------------------------------------------
;程序功能:任意輸入幾個字符(最多能輸入9個),按回車則倒著輸出!
data segment
user_string db 10,0,10 dup(?)
data ends
code segment
assume cs:code,ds:data
start: mov ax,data
mov ds,ax
lea dx,user_string ;建立輸入字符串緩沖區(qū)
mov ah,0ah
int 21h
xor si,si
xor bx,bx
mov bx,dx
mov cx,[bx+si+1] ;看這個就是"寄存器間接尋址方式"
xor ch,ch ;其目的就是取實際輸入字符個數(shù)
mov di,cx
lop: mov ah,2
mov dx,[bx+di+1];看這又是"寄存器間接尋址方式"
int 21h ;其目的就是取輸入字符串的最后一個字符
dec di
loop lop ;依次循環(huán)倒著輸出字符
mov ah,4ch
int 21h
code ends
end start
-----------------------------------------------------------------------
完工了
如果前面的編譯過程沒有錯誤的話,現(xiàn)在我們應(yīng)該已經(jīng)得到了一個HelloWDM.sys文件,假設(shè)它是放在D:\HelloWDM\objfre\i386中。
安裝WDM驅(qū)動程序可以用兩種方法,一種是利用注冊表,還有一種是利用INF文件。我們一般是采用INF文件(這是微軟推薦的)。INF文件可以在 WINNT\INF 目錄中找到很多。為了順利安裝,我在這里先給出 HelloWDM 所需要的 HelloWDM.INF 文件:
;; The Win2K DDK documentation contains an excellent INF reference. ;--------- Version Section --------------------------------------------------- [Version] Signature="$CHICAGO$" Provider=LC_Device DriverVer=8/21/2002,3.0.0.3 ; If device fits one of the standard classes, use the name and GUID here, ; otherwise create your own device class and GUID as this example shows. Class=Unknown ClassGUID={ff646f80-8def-11d2-9449-00105a075f6b} ;--------- SourceDiskNames and SourceDiskFiles Section ----------------------- ; These sections identify source disks and files for installation. They are ; shown here as an example, but commented out. [SourceDisksNames] 1 = "HelloWDM",Disk1,, [SourceDisksFiles] HelloWDM.sys = 1,objfre\i386, ;--------- ClassInstall/ClassInstall32 Section ------------------------------- ; Not necessary if using a standard class ; 9X Style [ClassInstall] Addreg=Class_AddReg ; NT Style [ClassInstall32] Addreg=Class_AddReg [Class_AddReg] HKR,,,,%DeviceClassName% HKR,,Icon,,"-5" ;--------- DestinationDirs Section ------------------------------------------- [DestinationDirs] YouMark_Files_Driver = 10,System32\Drivers ;--------- Manufacturer and Models Sections ---------------------------------- [Manufacturer] %MfgName%=Mfg0 [Mfg0] ; PCI hardware Ids use the form ; PCI\VEN_aaaa&DEV_bbbb&SUBSYS_cccccccc&REV_dd ;改成你自己的ID %DeviceDesc%=YouMark_DDI, PCI\VEN_9999&DEV_9999 ;---------- DDInstall Sections ----------------------------------------------- ; --------- Windows 9X ----------------- ; Experimentation has shown that DDInstall root names greater than 19 characters ; cause problems in Windows 98 [YouMark_DDI] CopyFiles=YouMark_Files_Driver AddReg=YouMark_9X_AddReg [YouMark_9X_AddReg] HKR,,DevLoader,,*ntkern HKR,,NTMPDriver,,HelloWDM.sys HKR, "Parameters", "BreakOnEntry", 0x00010001, 0 ; --------- Windows NT ----------------- [YouMark_DDI.NT] CopyFiles=YouMark_Files_Driver AddReg=YouMark_NT_AddReg [YouMark_DDI.NT.Services] Addservice = HelloWDM, 0x00000002, YouMark_AddService [YouMark_AddService] DisplayName = %SvcDesc% ServiceType = 1 ; SERVICE_KERNEL_DRIVER StartType = 3 ; SERVICE_DEMAND_START ErrorControl = 1 ; SERVICE_ERROR_NORMAL ServiceBinary = %10%\System32\Drivers\HelloWDM.sys [YouMark_NT_AddReg] HKLM, "System\CurrentControlSet\Services\HelloWDM\Parameters",\ "BreakOnEntry", 0x00010001, 0 ; --------- Files (common) ------------- [YouMark_Files_Driver] HelloWDM.sys ;--------- Strings Section --------------------------------------------------- [Strings] ProviderName="Flying L Co.,Ltd." MfgName="LC Soft" DeviceDesc="Hello World WDM!" DeviceClassName="LC_Device" SvcDesc="???" |
注意它可以同時在Win98或者Win2000中使用(系統(tǒng)會通過這個INF文件里面的字段名稱,自動選擇適合當(dāng)前系統(tǒng)的安裝方法的)。關(guān)于INF文件的各個字段含義現(xiàn)在我也不知道,所以也沒有辦法說清楚,如果誰看到這篇文章,而又知道的話,不妨為我一份。
準(zhǔn)備好這個 HelloWDM.INF 文件后,讓我們打開控制面板,雙擊“添加/刪除硬件”,選擇“添加/排除設(shè)備故障”->“添加新設(shè)備”->“否,我想從列表選擇硬件”->“其它設(shè)備”->“從磁盤安裝”,選擇 HelloWDM.INF 所在的路徑,然后安裝。
當(dāng)安裝完成后,系統(tǒng)就會添加上你寫好的驅(qū)動程序了。(可以在“設(shè)備管理器”中查看到)。然后重啟電腦,這個驅(qū)動程序就投入使用啦。
關(guān)于安裝,我也只知道這么多,到底安裝驅(qū)動程序時,操作系統(tǒng)都作了些什么,我也不是很清楚,等我弄明白了我再貼上。
DDK分為98 DDK和2000 DDK兩種,它們工作起來是大同小異的,不過有些驅(qū)動程序只能在2000 DDK中使用。由于Win98注定是一種即將被淘汰的操作系統(tǒng)了,所以我學(xué)習(xí)的時候也沒有過多的關(guān)注,我用的是2000的DDK,所以以下的所有內(nèi)容都是針對2000 DDK的。
·準(zhǔn)備工作
1、確定你已經(jīng)安裝了Visual C++
2、安裝2000 DDK
3、安裝2000 DDK成功后,在“開始”->“程序”里應(yīng)該有“Development Kits”->“Windows 2000 DDK”的項目。
(注意一定要先安裝好VC,然后才安裝DDK,這個順序決不能顛倒!!)
4、保證DDKROOT環(huán)境變量設(shè)置為Windows 2000 DDK的基目錄,如果不是的話,請在控制面板“系統(tǒng)”屬性的“高級”標(biāo)簽環(huán)境變量編輯器中設(shè)置好這個環(huán)境變量。
·編寫必需的文件
編譯WDM程序的時候,有兩個文件是必須要有的,它們是:
1、makefile
(這個是什么啊?你可能會問。)對于比較年輕的程序員來說,有可能沒有見過這個文件吧。其實在VC這些IDE出現(xiàn)之前,我們都必須使用makefile來確定項目中哪些文件需要重新編譯,現(xiàn)在的IDE都把這個工作自動做好了
我們要做的工作很簡單,就是提供這樣一個文件,它的內(nèi)容是:
# # DO NOT EDIT THIS FILE!!! Edit .\sources. If you want to add a new source # file to this component. This file merely indirects to the real make file # that is shared by all the driver components of the Windows NT DDK # !INCLUDE $(NTMAKEENV)\makefile.def |
TARGETNAME=HelloWDM //編譯出來的驅(qū)動程序的名稱 TARGETTYPE=DRIVER //編譯的類型是驅(qū)動程序編譯 DRIVERTYPE=WDM //驅(qū)動程序的類型是WDM驅(qū)動程序 TARGETPATH=OBJ //生成的文件存放在OBJ目錄中 INCLUDES=$(BASEDIR)\inc;\ //這是需要引入的頭文件 $(BASEDIR)\inc\ddk;\ TARGETLIBS=$(BASEDIR)\lib\*\free\usbd.lib\ //這是需要引入的庫文件 SOURCES=HelloWDM.cpp\ //這是源碼文件 |
New or updated MSVC detected. Updating DDK environment…. Setting environment for using Microsoft Visual C++ tools. Starting dirs creation…Completed. D:\NTDDK>cd\HelloWDM (回車) D:\HelloWDM>build (回車) |
首先祭出Delphi里附帶的tdump.exe程序。讓我們鍵入:
C:\WINNT\System32\Drivers>tdump ccport.sys -em -ee
參數(shù)-em是列出Import table,-ee是列出Export table。回車之后,屏幕列出一大堆東西:
C:\WINNT\SYSTEM32\DRIVERS>tdump ccport.sys -em -ee Turbo Dump Version 5.0.16.12 Copyright ? 1988, 2000 Inprise Corporation Display of File CCPORT.SYS IMPORT: NTOSKRNL.EXE={hint:011Fh}.’memcpy’ IMPORT: NTOSKRNL.EXE={hint:003Dh}.’IoDeleteDevice’ IMPORT: NTOSKRNL.EXE={hint:0030h}.’IoAttachDeviceToDeviceStack’ IMPORT: NTOSKRNL.EXE={hint:008Eh}.’KeSetEvent’ IMPORT: NTOSKRNL.EXE={hint:0068h}.’IofCallDriver’ IMPORT: NTOSKRNL.EXE={hint:0095h}.’KeWaitForSingleObject’ IMPORT: NTOSKRNL.EXE={hint:0074h}.’KeInitializeEvent’ IMPORT: NTOSKRNL.EXE={hint:003Fh}.’IoDetachDevice’ IMPORT: NTOSKRNL.EXE={hint:00D3h}.’RtlFreeUnicodeString’ IMPORT: NTOSKRNL.EXE={hint:0077h}.’KeInitializeSpinLock’ IMPORT: NTOSKRNL.EXE={hint:0129h}.’strcpy’ IMPORT: NTOSKRNL.EXE={hint:0121h}.’memset’ IMPORT: NTOSKRNL.EXE={hint:003Ch}.’IoCreateUnprotectedSymbolicLink’ IMPORT: NTOSKRNL.EXE={hint:0038h}.’IoCreateDevice’ IMPORT: NTOSKRNL.EXE={hint:00C2h}.’RtlAnsiStringToUnicodeString’ IMPORT: NTOSKRNL.EXE={hint:0069h}.’IofCompleteRequest’ IMPORT: NTOSKRNL.EXE={hint:0124h}.’sprintf’ IMPORT: NTOSKRNL.EXE={hint:003Eh}.’IoDeleteSymbolicLink’ IMPORT: NTOSKRNL.EXE={hint:0042h}.’IoFreeIrp’ IMPORT: NTOSKRNL.EXE={hint:004Dh}.’IoInitializeIrp’ IMPORT: NTOSKRNL.EXE={hint:002Dh}.’IoAllocateIrp’ IMPORT: NTOSKRNL.EXE={hint:0027h}.’InterlockedExchange’ IMPORT: NTOSKRNL.EXE={hint:0025h}.’InterlockedCompareExchange’ IMPORT: NTOSKRNL.EXE={hint:0035h}.’IoCancelIrp’ IMPORT: NTOSKRNL.EXE={hint:012Ah}.’strlen’ IMPORT: NTOSKRNL.EXE={hint:0126h}.’strcat’ IMPORT: NTOSKRNL.EXE={hint:0114h}.’atoi’ IMPORT: NTOSKRNL.EXE={hint:0128h}.’strcmp’ IMPORT: NTOSKRNL.EXE={hint:0034h}.’IoBuildSynchronousFsdRequest’ IMPORT: NTOSKRNL.EXE={hint:00D5h}.’RtlInitAnsiString’ IMPORT: HAL.DLL={hint:0006h}.’KfAcquireSpinLock’ IMPORT: HAL.DLL={hint:0009h}.’KfReleaseSpinLock’ EXPORT ord:0001=’Vcomm_DriverControl’ |
看到了嗎?它主要調(diào)用了NTOSKRNL.EXE和HAL.DLL文件(實際上你會發(fā)現(xiàn),幾乎所有的WDM驅(qū)動程序都會調(diào)用NTOSKRNL.EXE文件,從它的名字你可以看出為什么了吧?),并且輸出了一個函數(shù)“Vcomm_DriverControl”。這表明,其實.sys跟.exe文件一樣,都是一種PE文件來的。不同的是,.sys文件Import的通常是NTOSKRNL.EXE,而.exe文件Import的通常是KERNEL32.DLL和USER32.DLL。
知道了這些有什么用呢?實際上,由于.sys通常不調(diào)用KERNEL32.DLL和USER32.DLL,所以你是不能在設(shè)備驅(qū)動程序里面調(diào)用任何C、C++和Win32函數(shù)的,而且也不能用C++關(guān)鍵字new和delete等(可以用malloc和free來代替),而必須使用大量的內(nèi)核函數(shù)。另外,你應(yīng)該也能看到她調(diào)用了像IoDeleteDevice、IoAttachDeviceToDeviceStack等等函數(shù),這些你以前可能沒有見過的函數(shù)都是些內(nèi)核函數(shù)。為了讀者的方便,下面我列出一些常見的驅(qū)動程序可用的內(nèi)核函數(shù):
Ex… 執(zhí)行支持 Hal… 硬件抽象層(僅NT/Windows 2000) Io… I/O管理器(包括即插即用函數(shù)) Ke… 內(nèi)核 Ks… 內(nèi)核流IRP管理函數(shù) Mm… 內(nèi)存管理器 Ob… 對象管理器 Po… 電源管理 Ps… 進程結(jié)構(gòu) Rtl… 運行時庫 Se… 安全引用監(jiān)視 Zw… 其他函數(shù) |
最后讓我們再來看看,寫設(shè)備驅(qū)動程序時必須注意的一些問題:
1、內(nèi)核宏
如果查看DDK頭文件,會發(fā)現(xiàn)有幾個內(nèi)核函數(shù)是以宏的方式實現(xiàn)的。這種宏中有幾個宏的定義是相當(dāng)糟糕的。例如,我們看到RemoveHeadList的定義如下:
#define RemoveHeadList(ListHead) (ListHead)->Flink; {RemoveEntryList((ListHead)->Flink)} |
如果以以下方式調(diào)用RemoveHeadList,則將編譯錯誤的代碼:
if(SomethingInList) Entry = RemoveHeadList(list); |
使這個調(diào)用安全的唯一方法是使用花括號:
if(SomethingInList) { Entry = RemoveHeadList(list); } |
所以我們切勿為了貪圖一時的方便,而使用不太規(guī)范的寫法,最好是在所有的if、for和while等語句中使用花括號。
2、驅(qū)動程序函數(shù)名稱
跟C/C++的main()函數(shù)一樣,設(shè)備驅(qū)動程序也有一個必須存在,而且只能以DriverEntry()為名稱的入口函數(shù)。然而,除此之外,我們可以使用任何名字來給其他函數(shù)命名——只要你自己記得就行了,當(dāng)然,最好符合某些特定的規(guī)范啦,例如匈牙利命名法……
3、安裝時的問題
·在Windows98中驅(qū)動程序可執(zhí)行文件必須是8.3文件名。(別問我為什么,我也不知道,我只能建議你去問比爾該死)
·如果INF文件中含有非法節(jié)的詳細(xì)資料,Windows將不使用這個INF文件。
本節(jié)羅羅嗦嗦講了一大堆,跟實際的編程卻并沒有太大的關(guān)系,前傳嘛!就是這樣的啦!