一.關(guān)于H-sync /V-Sync的知識(shí):
1. 分辨率:比如說(shuō)640x480,就會(huì)有640 個(gè)pixel &480 line,那么每個(gè)V-sync的信號(hào)時(shí)間內(nèi)就會(huì)有480個(gè)H-sync,而一個(gè)H-sync會(huì)有640個(gè)pixel。但是,每個(gè)pixel會(huì)有2 byte,所以我們會(huì)量到PCLK 在一個(gè)H-sync內(nèi)的數(shù)量會(huì)有1280個(gè)。
2. H-sync /V-Sync的極性polarity: polarity就是資料有效的準(zhǔn)備,比方說(shuō)V-sync上的H-sync有可能在V-sync的low,也有可能在high出現(xiàn)。
二. 所使用的ISP處理器簡(jiǎn)介:XXX838是一款isp(圖像信號(hào)處理器)ic,核心是一款arm7 process,提供自動(dòng)對(duì)焦,人臉識(shí)別等功能。BB通過(guò)i2c與其進(jìn)行命令類的數(shù)據(jù)通信,而sensor數(shù)據(jù)則通過(guò)CCIR總線傳輸給BB.
三. 25平臺(tái) camera處理流程學(xué)習(xí)
1. void cam_event_ind_hdlr(ilm_struct *ilm_ptr)// This function is to handle camera event indication.
在該函數(shù)中,通過(guò)camera_capture_mem_process(&capture_mem_param)命令從lcd層獲取capture數(shù)據(jù),然后通過(guò)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 :所需存儲(chǔ)的拍照數(shù)據(jù)指針
cam_context_p->file_size :所要存儲(chǔ)的數(shù)據(jù)大小
4. 在cam_open_image_file函數(shù)中執(zhí)行命令
cam_context_p->capture_buffer_p = (kal_uint32) med_alloc_ext_mem(buffer_size);
來(lái)分配內(nèi)存。
Capture數(shù)據(jù)存儲(chǔ)指針: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)鍵點(diǎ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)建一個(gè)新函數(shù),在該函數(shù)中對(duì)獲取的數(shù)據(jù)直接存儲(chǔ),而不經(jīng)過(guò)jpeg編碼流程(由于XXX838傳輸過(guò)來(lái)的已經(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信號(hào)。
(3).此時(shí),獲取的capture的數(shù)據(jù)已經(jīng)存儲(chǔ)在isp_data->target_buffer_start_address中;
然后讀取這些數(shù)據(jù),通過(guò)0xff ,0xd8判斷文件頭,0xff ,0xd9判斷jpeg文件尾及其長(zhǎng)度。
(4)最后,通過(guò)kal_int32 cam_close_image_file(kal_uint32 size)保存文件
一、協(xié)商和解
協(xié)商和解的定義
????消費(fèi)者與經(jīng)營(yíng)者在發(fā)生爭(zhēng)議后,就與爭(zhēng)議有關(guān)的問(wèn)題進(jìn)行協(xié)商,在自愿、互諒的基礎(chǔ)上,通過(guò)直接對(duì)話擺事實(shí)、講道理,分清責(zé)任,達(dá)成和解協(xié)議,使糾紛得以解決的活動(dòng)。消費(fèi)者權(quán)益爭(zhēng)議的協(xié)商和解是一種快速、簡(jiǎn)便的爭(zhēng)議解決方式,無(wú)論是對(duì)消費(fèi)者還是對(duì)經(jīng)營(yíng)者,它都不失為一種理想的途徑。事實(shí)上,日常生活中大量的消費(fèi)者權(quán)益爭(zhēng)議都是通過(guò)這種方式解決的。
消費(fèi)者與經(jīng)營(yíng)者協(xié)商和解的法律依據(jù)
????《消費(fèi)者權(quán)益保護(hù)法》第34條明確規(guī)定:"消費(fèi)者和經(jīng)營(yíng)者發(fā)生消費(fèi)者權(quán)益爭(zhēng)議的,可以通過(guò)下列途徑解決:(1)與經(jīng)營(yíng)者協(xié)商和解;(2)請(qǐng)求消費(fèi)者協(xié)會(huì)調(diào)解;(3)向有關(guān)行政部門申訴;(4)根據(jù)與經(jīng)營(yíng)者達(dá)成的仲裁協(xié)議提請(qǐng)仲裁機(jī)構(gòu)仲裁;(5)向人民法院提起訴訟。"此條第1項(xiàng)規(guī)定,即"與經(jīng)營(yíng)者協(xié)商和解",便是消費(fèi)者與經(jīng)營(yíng)者協(xié)商和解的法律依據(jù)。
協(xié)商和解的步驟
????在實(shí)踐中,協(xié)商和解可以在其權(quán)益受到侵犯時(shí),帶上有關(guān)證據(jù),如購(gòu)貨憑證或者服務(wù)單據(jù)以及受損失證據(jù),找到經(jīng)營(yíng)者,向其負(fù)責(zé)人或者主管解決糾紛的部門說(shuō)明情況,并提出自己的意見(jiàn)和要求。如果經(jīng)營(yíng)者覺(jué)得消費(fèi)者的意見(jiàn)和要求合理,就會(huì)接受。如果經(jīng)營(yíng)者覺(jué)得消費(fèi)者的要求過(guò)高,就會(huì)要求消費(fèi)者降低其要求。經(jīng)過(guò)一番"討價(jià)還價(jià)"后,達(dá)成一個(gè)雙方都愿意接受的協(xié)議時(shí),爭(zhēng)議就解決了。
協(xié)商和解應(yīng)堅(jiān)持協(xié)作和平等原則:
????協(xié)作原則。要求消費(fèi)者與經(jīng)營(yíng)者在融洽的氣氛中,在互相諒解的基礎(chǔ)上,本著實(shí)事求是、團(tuán)結(jié)協(xié)作的精神,通過(guò)擺事實(shí)講道理,弄清事實(shí),分清責(zé)任,自愿地達(dá)成協(xié)議,避免只從自己一方的利益出發(fā),堅(jiān)持已見(jiàn),互不相讓。
????平等原則。消費(fèi)者和經(jīng)營(yíng)者要在平等的前提下自行協(xié)商解決消費(fèi)者權(quán)益爭(zhēng)議。決不允許任何一方憑借某種勢(shì)力,以強(qiáng)凌弱,以大壓小,享有特權(quán),獲得不平等的利益。
在協(xié)商和解時(shí),消費(fèi)者應(yīng)注意以下問(wèn)題:
????針對(duì)經(jīng)營(yíng)者故意拖延或無(wú)理拒絕消費(fèi)者協(xié)商和解建議的行為,消費(fèi)者應(yīng)立即采取措施,用其他途徑解決爭(zhēng)議問(wèn)題。即可用投訴、申訴或仲裁、起訴手段解決糾紛。如果經(jīng)營(yíng)者的故意拖延和無(wú)理拒絕,致使消費(fèi)者財(cái)產(chǎn)損失擴(kuò)大的,經(jīng)營(yíng)者除了應(yīng)當(dāng)滿足消費(fèi)者正常要求外,還應(yīng)當(dāng)就擴(kuò)大的損失承擔(dān)賠償責(zé)任。
????針對(duì)經(jīng)營(yíng)者故意推卸責(zé)任,認(rèn)為產(chǎn)品出現(xiàn)質(zhì)量問(wèn)題是生產(chǎn)廠家的事,要求消費(fèi)者直接找廠家交涉的行為,按《消費(fèi)者權(quán)益保護(hù)法》第35條規(guī)定:"消費(fèi)者在購(gòu)買、使用商品時(shí),其合法權(quán)益受到損害的,可以向銷售者要求賠償。銷售者賠償后,屬于生產(chǎn)者的責(zé)任或者屬于向銷售者提供商品的其他銷售者的責(zé)任的,銷售者有權(quán)向生產(chǎn)者或者其他銷售者追償。消費(fèi)者或者其他受害人因商品缺陷造成人身、財(cái)產(chǎn)損害的,可以向銷售者要求賠償,也可以向生產(chǎn)者要求賠償。屬于生產(chǎn)者責(zé)任的,銷售者賠償后,有權(quán)向生產(chǎn)者追償。屬于銷售者責(zé)任的,生產(chǎn)者賠償后,有權(quán)向銷售者追償。消費(fèi)者在接受服務(wù)時(shí),其合法權(quán)益受到損害的,可以向服務(wù)者要求賠償。"因此,當(dāng)消費(fèi)者遇到商品質(zhì)量問(wèn)題時(shí),如經(jīng)營(yíng)者推卸責(zé)任,認(rèn)為是生產(chǎn)廠家的問(wèn)題,要求消費(fèi)者直接找廠家交涉時(shí),消費(fèi)者應(yīng)當(dāng)有自我保護(hù)意識(shí),不能挾在中間讓廠家和經(jīng)營(yíng)者當(dāng)"皮球"踢。要以法律規(guī)定為依據(jù),切實(shí)維護(hù)自己的合法權(quán)益。
????針對(duì)經(jīng)營(yíng)者以店堂通知、聲明、告示為由,拒不承擔(dān)責(zé)任的行為,按《消費(fèi)者權(quán)益保護(hù)法》第24條規(guī)定:"經(jīng)營(yíng)者不得以格式合同、通知、聲明、店堂告示等方式作出對(duì)消費(fèi)者不公平、不合理的規(guī)定,或者減輕、免除其損害消費(fèi)者合法權(quán)益應(yīng)當(dāng)承擔(dān)的民事責(zé)任。格式合同、通知、聲明、店堂告示等含有前款所列內(nèi)容的,其內(nèi)容無(wú)效。"因此,當(dāng)消費(fèi)者因商品質(zhì)量和服務(wù)問(wèn)題與商家交涉、協(xié)商時(shí),千萬(wàn)不能為其店堂內(nèi)服務(wù)規(guī)則或商品銷售告示所約束,這些服務(wù)規(guī)則與法無(wú)據(jù),沒(méi)有法律效力,應(yīng)視為無(wú)效規(guī)則。
二、投訴和調(diào)解
投訴的定義:
????消費(fèi)者投訴,是指消費(fèi)者為生活消費(fèi)需要購(gòu)買、使用商品或者接受服務(wù),與經(jīng)營(yíng)者之間發(fā)生消費(fèi)者權(quán)益爭(zhēng)議后,請(qǐng)求消費(fèi)者權(quán)益保護(hù)組織調(diào)解,要求保護(hù)其合法權(quán)益的行為。
調(diào)解的含義:
????調(diào)解,即由第三方對(duì)爭(zhēng)議雙方當(dāng)事人進(jìn)行說(shuō)服勸導(dǎo)、勾通調(diào)和,以促成爭(zhēng)議雙方達(dá)成解決糾紛的協(xié)議的活動(dòng)。
《消費(fèi)者權(quán)益保護(hù)法》規(guī)定,消費(fèi)者爭(zhēng)議可以通過(guò)消費(fèi)者協(xié)會(huì)調(diào)解解決。實(shí)際上,消費(fèi)者糾紛的調(diào)解并非只能由消費(fèi)者協(xié)會(huì)進(jìn)行,任何第三人參與消費(fèi)者糾紛的解決,促成爭(zhēng)議雙方達(dá)成協(xié)議的,都屬調(diào)解的范圍。并且只要不存在違法行為,則調(diào)解同樣受法律承認(rèn)。
調(diào)解的原則:
????1、自愿原則。調(diào)解應(yīng)建立在雙方自愿的基礎(chǔ)之上。調(diào)解不同于審判,當(dāng)任何一方不同意調(diào)解時(shí),應(yīng)終止調(diào)解,而不得以任何理由加以強(qiáng)迫。
????2、合法原則。調(diào)解活動(dòng)應(yīng)在合法的原則上進(jìn)行,既要有必要的靈活性,更要有高度的原則性,不能違反法律的規(guī)定來(lái)"和稀泥"。
投訴的形式:
????消費(fèi)者投訴可以采取電話、信函、面談、互聯(lián)網(wǎng)形式進(jìn)行。但無(wú)論采取哪種形式,都要講清楚以下內(nèi)容:一是投訴人基本情況。即投訴人的姓名、性別、聯(lián)系地址、聯(lián)系電話、郵政編碼等。二是被投訴方的基本情況。即被投訴方名稱、地址、電話等。三是購(gòu)買商品的時(shí)間、品牌、產(chǎn)地、規(guī)格、數(shù)量、價(jià)格等。四是受損害的具體情況、發(fā)現(xiàn)問(wèn)題的時(shí)間及與經(jīng)營(yíng)者交涉的經(jīng)過(guò)等。五是購(gòu)物憑證、保修卡、約定書(shū)復(fù)印件等。
三、行政申訴
申訴的定義
????消費(fèi)者和經(jīng)營(yíng)者發(fā)生權(quán)益爭(zhēng)議后,可以請(qǐng)求政府有關(guān)行政部門依行政程序解決爭(zhēng)議,與其他爭(zhēng)議解決途徑相比,申訴具有高效、快捷、力度強(qiáng)等特點(diǎn)。?
消費(fèi)者向政府有關(guān)行政部門申訴的法律依據(jù)
???《消費(fèi)者權(quán)益保護(hù)法》第34條的規(guī)定,消費(fèi)者和經(jīng)營(yíng)者發(fā)生消費(fèi)者權(quán)益爭(zhēng)議的,可以向有關(guān)行政部門申訴。
消費(fèi)者如何進(jìn)行申訴?
????消費(fèi)者決定申訴時(shí),應(yīng)依照商品和服務(wù)的性質(zhì)向具有相關(guān)職能的行政部門提出。消費(fèi)者申訴一般應(yīng)采用書(shū)面形式,一式兩份,并載明下列事項(xiàng):(1)消費(fèi)者的姓名、住址、電話號(hào)碼、郵政編碼;(2)被申訴人的名稱、地址、聯(lián)系電話、郵政編碼;(3)申訴的要求、理由及相關(guān)的事實(shí)根據(jù);(4)申訴的日期。必要時(shí),消費(fèi)者可委托代理人進(jìn)行申訴活動(dòng),但需向有關(guān)行政部門提交授權(quán)委托書(shū)。
????消費(fèi)者向有關(guān)行政部門提出申訴后,如果與經(jīng)營(yíng)者協(xié)商和解,達(dá)成和解協(xié)議的,可以撤回申訴,請(qǐng)求有關(guān)行政部門根據(jù)和解協(xié)議作出調(diào)解書(shū)。如果與經(jīng)營(yíng)者達(dá)成仲裁協(xié)議,可以撤回申訴,向仲裁機(jī)構(gòu)提請(qǐng)仲裁。如果想通過(guò)法律途徑解決,可以撤回申訴,向人民法院提起訴訟。
四、提請(qǐng)仲裁
仲裁的定義?
????雙方當(dāng)事人在爭(zhēng)議發(fā)生前或者爭(zhēng)議發(fā)生后達(dá)成的協(xié)議,自愿將他們之間的爭(zhēng)議提交雙方所同意的仲裁機(jī)構(gòu)居中調(diào)解,作出判斷或裁決的活動(dòng)。
仲裁的優(yōu)越性
仲裁具有當(dāng)事人意思自愿、程序簡(jiǎn)便、一裁終局、專家仲裁、費(fèi)用較低、保守機(jī)密、相互感情影響小等特征。
當(dāng)事人采取仲裁方式解決糾紛,應(yīng)注意以下幾點(diǎn):
(1)當(dāng)事人采用仲裁方式解決糾紛,應(yīng)當(dāng)是雙方自愿,并達(dá)成仲裁協(xié)議;
(2)向哪個(gè)仲裁組織提請(qǐng)仲裁,由當(dāng)事人協(xié)議選定;
(3)可以選擇或者委托仲裁組織指定仲裁員;
(4)可以自行和解,達(dá)成和解協(xié)議的,可以請(qǐng)求仲裁庭根據(jù)和解協(xié)議作出裁決書(shū),也可以撤回仲裁申請(qǐng)。
仲裁的原則和制度
仲裁實(shí)行自愿、獨(dú)立、公正、一裁終局的原則和制度。
仲裁協(xié)議的定義
????雙方當(dāng)事人自愿把他們之間的經(jīng)濟(jì)爭(zhēng)議提交仲裁解決的書(shū)面約定。其表現(xiàn)形式包括合同中訂立的仲裁條款和以其他書(shū)面方式在糾紛發(fā)生后前或者發(fā)生后達(dá)成請(qǐng)求的仲裁協(xié)議。仲裁協(xié)議是獨(dú)立存在的,合同的變更、解除、終止或者無(wú)效,不影響仲裁協(xié)議的效力。
仲裁協(xié)議應(yīng)具備的內(nèi)容
(1)請(qǐng)求仲裁的意思表示;
(2)仲裁事項(xiàng);
(3)選定的仲裁委員會(huì)。
當(dāng)事人提請(qǐng)仲裁應(yīng)當(dāng)符合的條件
(1)有仲裁協(xié)議;
(2)有具體的仲裁請(qǐng)求和事實(shí)理由;
(3)屬于仲裁委員會(huì)的管理范圍。
仲裁案件受理費(fèi)的承擔(dān)
????仲裁費(fèi)用原則上由敗訴的當(dāng)事人承擔(dān),當(dāng)事人部分勝訴,部分?jǐn)≡V的,由仲裁庭根據(jù)當(dāng)事人各方責(zé)任大小確定其各自應(yīng)當(dāng)承擔(dān)的仲裁費(fèi)用的比例,當(dāng)事人自行和解或者經(jīng)仲裁庭調(diào)解結(jié)案的,當(dāng)事人可以協(xié)商確定各自承擔(dān)的仲裁費(fèi)用的比例。
五、提起訴訟
提起訴訟的定義
????消費(fèi)者因其合法權(quán)益受到侵害后,可以向人民法院提起訴訟,請(qǐng)求人民法院依照法定程序進(jìn)行審判。在我國(guó),訴訟大致分為三種形式:(1)刑事訴訟;(2)民事訴訟;(3)行政訴訟。消費(fèi)者因其合法權(quán)益受到侵害而提起的訴訟屬于民事訴訟范疇。
提起訟訴必須具備的法定條件
(1)原告必須是與本案有直接利害關(guān)系的公民、法人和其他組織;
(2)有明確的被告;
(3)有具體的訴訟請(qǐng)求和事實(shí)、理由;
(1)屬于人民法院受理民事訴訟的范圍和受訴人民法院管轄。
符合以上條件的起訴,人民法院才會(huì)予以受理。
#define MIN(A,B) ((A) <= (B) (A) : (B))
這個(gè)測(cè)試是為下面的目的而設(shè)的:
1). 標(biāo)識(shí)#define在宏中應(yīng)用的基本知識(shí)。這是很重要的,因?yàn)橹钡角度?inline)操作符變?yōu)闃?biāo)準(zhǔn)C的一部分,宏是方便產(chǎn)生嵌入代碼的唯一方法,對(duì)于嵌入式系統(tǒng)來(lái)說(shuō),為了能達(dá)到要求的性能,嵌入代碼經(jīng)常是必須的方法。
2). 三重條件操作符的知識(shí)。這個(gè)操作符存在C語(yǔ)言中的原因是它使得編譯器能產(chǎn)生比if-then-else更優(yōu)化的代碼,了解這個(gè)用法是很重要的。
3). 懂得在宏中小心地把參數(shù)用括號(hào)括起來(lái)
4). 我也用這個(gè)問(wèn)題開(kāi)始討論宏的副作用,例如:當(dāng)你寫(xiě)下面的代碼時(shí)會(huì)發(fā)生什么事?
least = MIN(*p++, b);
3. 預(yù)處理器標(biāo)識(shí)#error的目的是什么?
如果你不知道答案,請(qǐng)看參考文獻(xiàn)1。這問(wèn)題對(duì)區(qū)分一個(gè)正常的伙計(jì)和一個(gè)書(shū)呆子是很有用的。只有書(shū)呆子才會(huì)讀C語(yǔ)言課本的附錄去找出象這種
問(wèn)題的答案。當(dāng)然如果你不是在找一個(gè)書(shū)呆子,那么應(yīng)試者最好希望自己不要知道答案。
死循環(huán)(Infinite loops)
4. 嵌入式系統(tǒng)中經(jīng)常要用到無(wú)限循環(huán),你怎么樣用C編寫(xiě)死循環(huán)呢?
這個(gè)問(wèn)題用幾個(gè)解決方案。我首選的方案是:
while(1) { }
一些程序員更喜歡如下方案:
for(;;) { }
這個(gè)實(shí)現(xiàn)方式讓我為難,因?yàn)檫@個(gè)語(yǔ)法沒(méi)有確切表達(dá)到底怎么回事。如果一個(gè)應(yīng)試者給出這個(gè)作為方案,我將用這個(gè)作為一個(gè)機(jī)會(huì)去探究他們這樣做的
基本原理。如果他們的基本答案是:“我被教著這樣做,但從沒(méi)有想到過(guò)為什么。”這會(huì)給我留下一個(gè)壞印象。
第三個(gè)方案是用 goto
Loop:
...
goto Loop;
應(yīng)試者如給出上面的方案,這說(shuō)明或者他是一個(gè)匯編語(yǔ)言程序員(這也許是好事)或者他是一個(gè)想進(jìn)入新領(lǐng)域的BASIC/FORTRAN程序員。
數(shù)據(jù)聲明(Data declarations)
5. 用變量a給出下面的定義
a) 一個(gè)整型數(shù)(An integer)
b) 一個(gè)指向整型數(shù)的指針(A pointer to an integer)
c) 一個(gè)指向指針的的指針,它指向的指針是指向一個(gè)整型數(shù)(A pointer to a pointer to an integer)
d) 一個(gè)有10個(gè)整型數(shù)的數(shù)組(An array of 10 integers)
e) 一個(gè)有10個(gè)指針的數(shù)組,該指針是指向一個(gè)整型數(shù)的(An array of 10 pointers to integers)
f) 一個(gè)指向有10個(gè)整型數(shù)數(shù)組的指針(A pointer to an array of 10 integers)
g) 一個(gè)指向函數(shù)的指針,該函數(shù)有一個(gè)整型參數(shù)并返回一個(gè)整型數(shù)(A pointer to a function that takes an integer as an argument and returns an integer)
h) 一個(gè)有10個(gè)指針的數(shù)組,該指針指向一個(gè)函數(shù),該函數(shù)有一個(gè)整型參數(shù)并返回一個(gè)整型數(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)常聲稱這里有幾個(gè)問(wèn)題是那種要翻一下書(shū)才能回答的問(wèn)題,我同意這種說(shuō)法。當(dāng)我寫(xiě)這篇文章時(shí),為了確定語(yǔ)法的正確性,我的確查了一下書(shū)。
但是當(dāng)我被面試的時(shí)候,我期望被問(wèn)到這個(gè)問(wèn)題(或者相近的問(wèn)題)。因?yàn)樵诒幻嬖嚨倪@段時(shí)間里,我確定我知道這個(gè)問(wèn)題的答案。應(yīng)試者如果不知道
所有的答案(或至少大部分答案),那么也就沒(méi)有為這次面試做準(zhǔn)備,如果該面試者沒(méi)有為這次面試做準(zhǔn)備,那么他又能為什么出準(zhǔn)備呢?
Static
6. 關(guān)鍵字static的作用是什么?
這個(gè)簡(jiǎn)單的問(wèn)題很少有人能回答完全。在C語(yǔ)言中,關(guān)鍵字static有三個(gè)明顯的作用:
1). 在函數(shù)體,一個(gè)被聲明為靜態(tài)的變量在這一函數(shù)被調(diào)用過(guò)程中維持其值不變。
2). 在模塊內(nèi)(但在函數(shù)體外),一個(gè)被聲明為靜態(tài)的變量可以被模塊內(nèi)所用函數(shù)訪問(wèn),但不能被模塊外其它函數(shù)訪問(wèn)。它是一個(gè)本地的全局變量。
3). 在模塊內(nèi),一個(gè)被聲明為靜態(tài)的函數(shù)只可被這一模塊內(nèi)的其它函數(shù)調(diào)用。那就是,這個(gè)函數(shù)被限制在聲明它的模塊的本地范圍內(nèi)使用。
大多數(shù)應(yīng)試者能正確回答第一部分,一部分能正確回答第二部分,同是很少的人能懂得第三部分。這是一個(gè)應(yīng)試者的嚴(yán)重的缺點(diǎn),因?yàn)樗@然不懂得本地化數(shù)據(jù)和代碼范圍的好處和重要性。
Const
7.關(guān)鍵字const是什么含意?
我只要一聽(tīng)到被面試者說(shuō):“const意味著常數(shù)”,我就知道我正在和一個(gè)業(yè)余者打交道。去年Dan Saks已經(jīng)在他的文章里完全概括了const的所有用法,因此ESP(譯者:Embedded Systems Programming)的每一位讀者應(yīng)該非常熟悉const能做什么和不能做什么.如果你從沒(méi)有讀到那篇文章,只要能說(shuō)出const意味著“只讀”就可以了。盡管這個(gè)答案不是完全的答案,但我接受它作為一個(gè)正確的答案。(如果你想知道更詳細(xì)的答案,仔細(xì)讀一下Saks的文章吧。)如果應(yīng)試者能正確回答這個(gè)問(wèn)題,我將問(wèn)他一個(gè)附加的問(wèn)題:下面的聲明都是什么意思?
const int a;
int const a;
const int *a;
int * const a;
int const * a const;
前兩個(gè)的作用是一樣,a是一個(gè)常整型數(shù)。第三個(gè)意味著a是一個(gè)指向常整型數(shù)的指針(也就是,整型數(shù)是不可修改的,但指針可以)。第四個(gè)意思a是一個(gè)指向整型數(shù)的常指針(也就是說(shuō),指針指向的整型數(shù)是可以修改的,但指針是不可修改的)。最后一個(gè)意味著a是一個(gè)指向常整型數(shù)的常指針(也就是說(shuō),指針指向的整型數(shù)是不可修改的,同時(shí)指針也是不可修改的)。如果應(yīng)試者能正確回答這些問(wèn)題,那么他就給我留下了一個(gè)好印象。順帶提一句,也許你可能會(huì)問(wèn),即使不用關(guān)鍵字const,也還是能很容易寫(xiě)出功能正確的程序,那么我為什么還要如此看重關(guān)鍵字const呢?我也如下的幾下理由:
1). 關(guān)鍵字const的作用是為給讀你代碼的人傳達(dá)非常有用的信息,實(shí)際上,聲明一個(gè)參數(shù)為常量是為了告訴了用戶這個(gè)參數(shù)的應(yīng)用目的。如果你曾花很多時(shí)間清理其它人留下的垃圾,你就會(huì)很快學(xué)會(huì)感謝這點(diǎn)多余的信息。(當(dāng)然,懂得用const的程序員很少會(huì)留下的垃圾讓別人來(lái)清理的。)
2). 通過(guò)給優(yōu)化器一些附加的信息,使用關(guān)鍵字const也許能產(chǎn)生更緊湊的代碼。
3). 合理地使用關(guān)鍵字const可以使編譯器很自然地保護(hù)那些不希望被改變的參數(shù),防止其被無(wú)意的代碼修改。簡(jiǎn)而言之,這樣可以減少bug的出現(xiàn)。
Volatile
8. 關(guān)鍵字volatile有什么含意 并給出三個(gè)不同的例子。
一個(gè)定義為volatile的變量是說(shuō)這變量可能會(huì)被意想不到地改變,這樣,編譯器就不會(huì)去假設(shè)這個(gè)變量的值了。精確地說(shuō)就是,優(yōu)化器在用到這個(gè)變量時(shí)必須每次都小心地重新讀取這個(gè)變量的值,而不是使用保存在寄存器里的備份。下面是volatile變量的幾個(gè)例子:
1). 并行設(shè)備的硬件寄存器(如:狀態(tài)寄存器)
2). 一個(gè)中斷服務(wù)子程序中會(huì)訪問(wèn)到的非自動(dòng)變量(Non-automatic variables)
3). 多線程應(yīng)用中被幾個(gè)任務(wù)共享的變量
回答不出這個(gè)問(wèn)題的人是不會(huì)被雇傭的。我認(rèn)為這是區(qū)分C程序員和嵌入式系統(tǒng)程序員的最基本的問(wèn)題。嵌入式系統(tǒng)程序員經(jīng)常同硬件、中斷、RTOS等等打交道,所用這些都要求volatile變量。不懂得volatile內(nèi)容將會(huì)帶來(lái)災(zāi)難。
假設(shè)被面試者正確地回答了這是問(wèn)題(嗯,懷疑這否會(huì)是這樣),我將稍微深究一下,看一下這家伙是不是直正懂得volatile完全的重要性。
1). 一個(gè)參數(shù)既可以是const還可以是volatile嗎?解釋為什么。
2). 一個(gè)指針可以是volatile 嗎?解釋為什么。
3). 下面的函數(shù)有什么錯(cuò)誤:
int square(volatile int *ptr)
{
return *ptr * *ptr;
}
下面是答案:
1). 是的。一個(gè)例子是只讀的狀態(tài)寄存器。它是volatile因?yàn)樗赡鼙灰庀氩坏降馗淖儭K莄onst因?yàn)槌绦虿粦?yīng)該試圖去修改它。
2). 是的。盡管這并不很常見(jiàn)。一個(gè)例子是當(dāng)一個(gè)中服務(wù)子程序修該一個(gè)指向一個(gè)buffer的指針時(shí)。
3). 這段代碼的有個(gè)惡作劇。這段代碼的目的是用來(lái)返指針*ptr指向值的平方,但是,由于*ptr指向一個(gè)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)總是要用戶對(duì)變量或寄存器進(jìn)行位操作。給定一個(gè)整型變量a,寫(xiě)兩段代碼,第一個(gè)設(shè)置a的bit 3,第二個(gè)清除a 的bit 3。在以上兩個(gè)操作中,要保持其它位不變。
對(duì)這個(gè)問(wèn)題有三種基本的反應(yīng)
1). 不知道如何下手。該被面者從沒(méi)做過(guò)任何嵌入式系統(tǒng)的工作。
2). 用bit fields。Bit fields是被扔到C語(yǔ)言死角的東西,它保證你的代碼在不同編譯器之間是不可移植的,同時(shí)也保證了的你的代碼是不可重用的。我最近不幸看到Infineon為其較復(fù)雜的通信芯片寫(xiě)的驅(qū)動(dòng)程序,它用到了bit fields因此完全對(duì)我無(wú)用,因?yàn)槲业木幾g器用其它的方式來(lái)實(shí)現(xiàn)bit fields的。從道德講:永遠(yuǎn)不要讓一個(gè)非嵌入式的家伙粘實(shí)際硬件的邊。
3). 用 #defines 和 bit masks 操作。這是一個(gè)有極高可移植性的方法,是應(yīng)該被用到的方法。最佳的解決方案如下:
#define BIT3 (0x1<<3)
static int a;
void set_bit3(void)
{
a |= BIT3;
}
void clear_bit3(void)
{
a &= ~BIT3;
}
一些人喜歡為設(shè)置和清除值而定義一個(gè)掩碼同時(shí)定義一些說(shuō)明常數(shù),這也是可以接受的。我希望看到幾個(gè)要點(diǎn):說(shuō)明常數(shù)、|=和&=~操作。
訪問(wèn)固定的內(nèi)存位置(Accessing fixed memory locations)
10. 嵌入式系統(tǒng)經(jīng)常具有要求程序員去訪問(wèn)某特定的內(nèi)存位置的特點(diǎn)。在某工程中,要求設(shè)置一絕對(duì)地址為0x67a9的整型變量的值為0xaa66。編譯器是一個(gè)純粹的ANSI編譯器。寫(xiě)代碼去完成這一任務(wù)。
這一問(wèn)題測(cè)試你是否知道為了訪問(wèn)一絕對(duì)地址把一個(gè)整型數(shù)強(qiáng)制轉(zhuǎn)換(typecast)為一指針是合法的。這一問(wèn)題的實(shí)現(xiàn)方式隨著個(gè)人風(fēng)格不同而不同。典型的類似代碼如下:
int *ptr;
ptr = (int *)0x67a9;
*ptr = 0xaa55;
一個(gè)較晦澀的方法是:
*(int * const)(0x67a9) = 0xaa55;
即使你的品味更接近第二種方案,但我建議你在面試時(shí)使用第一種方案。
中斷(Interrupts)
11. 中斷是嵌入式系統(tǒng)中重要的組成部分,這導(dǎo)致了很多編譯開(kāi)發(fā)商提供一種擴(kuò)展—讓標(biāo)準(zhǔn)C支持中斷。具代表事實(shí)是,產(chǎn)生了一個(gè)新的關(guān)鍵字__interrupt。下面的代碼就使用了__interrupt關(guān)鍵字去定義了一個(gè)中斷服務(wù)子程序(ISR),請(qǐng)?jiān)u論一下這段代碼的。
__interrupt double compute_area (double radius)
{
??? double area = PI * radius * radius;
??? printf(" Area = %f", area);
??? return area;
}
這個(gè)函數(shù)有太多的錯(cuò)誤了,以至讓人不知從何說(shuō)起了:
1). ISR 不能返回一個(gè)值。如果你不懂這個(gè),那么你不會(huì)被雇用的。
2). ISR 不能傳遞參數(shù)。如果你沒(méi)有看到這一點(diǎn),你被雇用的機(jī)會(huì)等同第一項(xiàng)。
3). 在許多的處理器/編譯器中,浮點(diǎn)一般都是不可重入的。有些處理器/編譯器需要讓額處的寄存器入棧,有些處理器/編譯器就是不允許在ISR中做浮點(diǎn)運(yùn)算。此外,ISR應(yīng)該是短而有效率的,在ISR中做浮點(diǎn)運(yùn)算是不明智的。
4). 與第三點(diǎn)一脈相承,printf()經(jīng)常有重入和性能上的問(wèn)題。如果你丟掉了第三和第四點(diǎn),我不會(huì)太為難你的。不用說(shuō),如果你能得到后兩點(diǎn),那么你的被雇用前景越來(lái)越光明了。
代碼例子(Code examples)
12 . 下面的代碼輸出是什么,為什么?
void foo(void)
{
??? unsigned int a = 6;
??? int b = -20;
??? (a+b > 6) puts("> 6") : puts("<= 6");
}
這個(gè)問(wèn)題測(cè)試你是否懂得C語(yǔ)言中的整數(shù)自動(dòng)轉(zhuǎn)換原則,我發(fā)現(xiàn)有些開(kāi)發(fā)者懂得極少這些東西。不管如何,這無(wú)符號(hào)整型問(wèn)題的答案是輸出是“>6”。原因是當(dāng)表達(dá)式中存在有符號(hào)類型和無(wú)符號(hào)類型時(shí)所有的操作數(shù)都自動(dòng)轉(zhuǎn)換為無(wú)符號(hào)類型。 因此-20變成了一個(gè)非常大的正整數(shù),所以該表達(dá)式計(jì)算出的結(jié)果大于6。這一點(diǎn)對(duì)于應(yīng)當(dāng)頻繁用到無(wú)符號(hào)數(shù)據(jù)類型的嵌入式系統(tǒng)來(lái)說(shuō)是豐常重要的。如果你答錯(cuò)了這個(gè)問(wèn)題,你也就到了得不到這份工作的邊緣。
13. 評(píng)價(jià)下面的代碼片斷:
unsigned int zero = 0;
unsigned int compzero = 0xFFFF;
/*1's complement of zero */
對(duì)于一個(gè)int型不是16位的處理器為說(shuō),上面的代碼是不正確的。應(yīng)編寫(xiě)如下:
unsigned int compzero = ~0;
這一問(wèn)題真正能揭露出應(yīng)試者是否懂得處理器字長(zhǎng)的重要性。在我的經(jīng)驗(yàn)里,好的嵌入式程序員非常準(zhǔn)確地明白硬件的細(xì)節(jié)和它的局限,然而PC機(jī)程序往往把硬件作為一個(gè)無(wú)法避免的煩惱。
到了這個(gè)階段,應(yīng)試者或者完全垂頭喪氣了或者信心滿滿志在必得。如果顯然應(yīng)試者不是很好,那么這個(gè)測(cè)試就在這里結(jié)束了。但如果顯然應(yīng)試者做得不錯(cuò),那么我就扔出下面的追加問(wèn)題,這些問(wèn)題是比較難的,我想僅僅非常優(yōu)秀的應(yīng)試者能做得不錯(cuò)。提出這些問(wèn)題,我希望更多看到應(yīng)試者應(yīng)付問(wèn)題的方法,而不是答案。不管如何,你就當(dāng)是這個(gè)娛樂(lè)吧…
動(dòng)態(tài)內(nèi)存分配(Dynamic memory allocation)
14. 盡管不像非嵌入式計(jì)算機(jī)那么常見(jiàn),嵌入式系統(tǒng)還是有從堆(heap)中動(dòng)態(tài)分配內(nèi)存的過(guò)程的。那么嵌入式系統(tǒng)中,動(dòng)態(tài)分配內(nèi)存可能發(fā)生的問(wèn)題是什么?
這里,我期望應(yīng)試者能提到內(nèi)存碎片,碎片收集的問(wèn)題,變量的持行時(shí)間等等。這個(gè)主題已經(jīng)在ESP雜志中被廣泛地討論過(guò)了(主要是 P.J. Plauger, 他的解釋遠(yuǎn)遠(yuǎn)超過(guò)我這里能提到的任何解釋),所有回過(guò)頭看一下這些雜志吧!讓?xiě)?yīng)試者進(jìn)入一種虛假的安全感覺(jué)后,我拿出這么一個(gè)小節(jié)目:下面的代碼片段的輸出是什么,為什么?
char *ptr;
if ((ptr = (char *)malloc(0)) == NULL)
puts("Got a null pointer");
else
puts("Got a valid pointer");
這是一個(gè)有趣的問(wèn)題。最近在我的一個(gè)同事不經(jīng)意把0值傳給了函數(shù)malloc,得到了一個(gè)合法的指針之后,我才想到這個(gè)問(wèn)題。這就是上面的代碼,該代碼的輸出是“Got a valid pointer”。我用這個(gè)來(lái)開(kāi)始討論這樣的一問(wèn)題,看看被面試者是否想到庫(kù)例程這樣做是正確。得到正確的答案固然重要,但解決問(wèn)題的方法和你做決定的基本原理更重要些。
Typedef
15. Typedef 在C語(yǔ)言中頻繁用以聲明一個(gè)已經(jīng)存在的數(shù)據(jù)類型的同義字。也可以用預(yù)處理器做類似的事。例如,思考一下下面的例子:
#define dPS struct s *
typedef struct s * tPS;
以上兩種情況的意圖都是要定義dPS 和 tPS 作為一個(gè)指向結(jié)構(gòu)s指針。哪種方法更好呢?(如果有的話)為什么?
這是一個(gè)非常微妙的問(wèn)題,任何人答對(duì)這個(gè)問(wèn)題(正當(dāng)?shù)脑颍┦菓?yīng)當(dāng)被恭喜的。答案是:typedef更好。思考下面的例子:
dPS p1,p2;
tPS p3,p4;
第一個(gè)擴(kuò)展為
struct s * p1, p2;
上面的代碼定義p1為一個(gè)指向結(jié)構(gòu)的指,p2為一個(gè)實(shí)際的結(jié)構(gòu),這也許不是你想要的。第二個(gè)例子正確地定義了p3 和p4 兩個(gè)指針。
晦澀的語(yǔ)法
16. C語(yǔ)言同意一些令人震驚的結(jié)構(gòu),下面的結(jié)構(gòu)是合法的嗎,如果是它做些什么?
int a = 5, b = 7, c;
c = a+++b;
這個(gè)問(wèn)題將做為這個(gè)測(cè)驗(yàn)的一個(gè)愉快的結(jié)尾。不管你相不相信,上面的例子是完全合乎語(yǔ)法的。問(wèn)題是編譯器如何處理它?水平不高的編譯作者實(shí)際上會(huì)爭(zhēng)論這個(gè)問(wèn)題,根據(jù)最處理原則,編譯器應(yīng)當(dāng)能處理盡可能所有合法的用法。因此,上面的代碼被處理成:
c = a++ + b;
因此, 這段代碼持行后a = 6, b = 7, c = 12。
如果你知道答案,或猜出正確答案,做得好。如果你不知道答案,我也不把這個(gè)當(dāng)作問(wèn)題。我發(fā)現(xiàn)這個(gè)問(wèn)題的最大好處是:這是一個(gè)關(guān)于代碼編寫(xiě)風(fēng)格,代碼的可讀性,代碼的可修改性的好的話題
對(duì)初學(xué)者而言,匯編的許多命令太復(fù)雜,往往學(xué)習(xí)很長(zhǎng)時(shí)間也寫(xiě)不出一個(gè)漂漂亮亮的程序,以致妨礙了我們學(xué)習(xí)匯編的興趣,不少人就此放棄。所以我個(gè)人看法學(xué)匯編,不一定要寫(xiě)程序,寫(xiě)程序確實(shí)不是匯編的強(qiáng)項(xiàng),大家不妨玩玩DEBUG,有時(shí)CRACK出一個(gè)小軟件比完成一個(gè)程序更有成就感(就像學(xué)電腦先玩游戲一樣)。某些高深的指令事實(shí)上只對(duì)有經(jīng)驗(yàn)的匯編程序員有用,對(duì)我們而言,太過(guò)高深了。為了使學(xué)習(xí)匯編語(yǔ)言有個(gè)好的開(kāi)始,你必須要先排除那些華麗復(fù)雜的命令,將注意力集中在最重要的幾個(gè)指令上(CMP LOOP MOV JNZ……)。但是想在啰里吧嗦的教科書(shū)中完成上述目標(biāo),談何容易,所以本人整理了這篇超濃縮(用WINZIP、WINRAR…依次壓迫,嘿嘿!)教程。大言不慚的說(shuō),看通本文,你完全可以“不經(jīng)意”間在前輩或是后生賣弄一下DEBUG,很有成就感的,試試看!那么――這個(gè)接下來(lái)呢?―― Here we go!(閱讀時(shí)看不懂不要緊,下文必有分解)
因?yàn)閰R編是通過(guò)CPU和內(nèi)存跟硬件對(duì)話的,所以我們不得不先了解一下CPU和內(nèi)存:(關(guān)于數(shù)的進(jìn)制問(wèn)題在此不提)
CPU是可以執(zhí)行電腦所有算術(shù)╱邏輯運(yùn)算與基本 I/O 控制功能的一塊芯片。一種匯編語(yǔ)言只能用于特定的CPU。也就是說(shuō),不同的CPU其匯編語(yǔ)言的指令語(yǔ)法亦不相同。個(gè)人電腦由1981年推出至今,其CPU發(fā)展過(guò)程為:8086→80286→80386→80486→PENTIUM →……,還有AMD、CYRIX等旁支。后面兼容前面CPU的功能,只不過(guò)多了些指令(如多能奔騰的MMX指令集)、增大了寄存器(如386的32位EAX)、增多了寄存器(如486的FS)。為確保匯編程序可以適用于各種機(jī)型,所以推薦使用8086匯編語(yǔ)言,其兼容性最佳。本文所提均為8086匯編語(yǔ)言。寄存器(Register)是CPU內(nèi)部的元件,所以在寄存器之間的數(shù)據(jù)傳送非常快。用途:1.可將寄存器內(nèi)的數(shù)據(jù)執(zhí)行算術(shù)及邏輯運(yùn)算。2.存于寄存器內(nèi)的地址可用來(lái)指向內(nèi)存的某個(gè)位置,即尋址。3.可以用來(lái)讀寫(xiě)數(shù)據(jù)到電腦的周邊設(shè)備。8086 有8個(gè)8位數(shù)據(jù)寄存器,這些8位寄存器可分別組成16位寄存器:AH&AL=AX:累加寄存器,常用于運(yùn)算;BH&BL=BX:基址寄存器,常用于地址索引;CH&CL=CX:計(jì)數(shù)寄存器,常用于計(jì)數(shù);DH&DL=DX:數(shù)據(jù)寄存器,常用于數(shù)據(jù)傳遞。為了運(yùn)用所有的內(nèi)存空間,8086設(shè)定了四個(gè)段寄存器,專門用來(lái)保存段地址:CS(Code Segment):代碼段寄存器;DS(Data Segment):數(shù)據(jù)段寄存器;SS(Stack Segment):堆棧段寄存器;ES(Extra Segment):附加段寄存器。當(dāng)一個(gè)程序要執(zhí)行時(shí),就要決定程序代碼、數(shù)據(jù)和堆棧各要用到內(nèi)存的哪些位置,通過(guò)設(shè)定段寄存器 CS,DS,SS 來(lái)指向這些起始位置。通常是將DS固定,而根據(jù)需要修改CS。所以,程序可以在可尋址空間小于64K的情況下被寫(xiě)成任意大小。 所以,程序和其數(shù)據(jù)組合起來(lái)的大小,限制在DS 所指的64K內(nèi),這就是COM文件不得大于64K的原因。8086以內(nèi)存做為戰(zhàn)場(chǎng),用寄存器做為軍事基地,以加速工作。除了前面所提的寄存器外,還有一些特殊功能的寄存器:IP(Intruction Pointer):指令指針寄存器,與CS配合使用,可跟蹤程序的執(zhí)行過(guò)程;SP(Stack Pointer):堆棧指針,與SS配合使用,可指向目前的堆棧位置。BP(Base Pointer):基址指針寄存器,可用作SS的一個(gè)相對(duì)基址位置;SI(Source Index):源變址寄存器可用來(lái)存放相對(duì)于DS段之源變址指針;DI(Destination Index):目的變址寄存器,可用來(lái)存放相對(duì)于 ES 段之目的變址指針。還有一個(gè)標(biāo)志寄存器FR(Flag Register),有九個(gè)有意義的標(biāo)志,將在下文用到時(shí)詳細(xì)說(shuō)明。
內(nèi)存是電腦運(yùn)作中的關(guān)鍵部分,也是電腦在工作中儲(chǔ)存信息的地方。內(nèi)存組織有許多可存放數(shù)值的儲(chǔ)存位置,叫“地址”。8086地址總線有20位,所以CPU擁有達(dá)1M的尋址空間,這也是DOS的有效控制范圍,而8086能做的運(yùn)算僅限于處理16位數(shù)據(jù),即只有0到64K,所以,必須用分段尋址才能控制整個(gè)內(nèi)存地址。完整的20位地址可分成兩部份:1.段基址(Segment):16位二進(jìn)制數(shù)后面加上四個(gè)二進(jìn)制0,即一個(gè)16進(jìn)制0,變成20位二進(jìn)制數(shù),可設(shè)定1M中任何一個(gè)64K段,通常記做16位二進(jìn)制數(shù);2.偏移量(Offset):直接使用16位二進(jìn)制數(shù),指向段基址中的任何一個(gè)地址。如:2222(段基址):3333(偏移量),其實(shí)際的20位地址值為:25553。除了上述營(yíng)養(yǎng)要充分吸收外,你還要知道什么是DOS、BIOS功能調(diào)用,簡(jiǎn)單的說(shuō),功能調(diào)用類似于WIN95 API,相當(dāng)于子程序。匯編寫(xiě)程序已經(jīng)夠要命了,如果不用MS、IBM的子程序,這日子真是沒(méi)法過(guò)了(關(guān)于功能調(diào)用詳見(jiàn)《電腦愛(ài)好者》98年11期)。
編寫(xiě)匯編語(yǔ)言有兩種主要的方法:1.使用MASM或TASM等編譯器;2.使用除錯(cuò)程序DEBUG.COM。DEBUG其實(shí)并不能算是一個(gè)編譯器,它的主要用途在于除錯(cuò),即修正匯編程序中的錯(cuò)誤。不過(guò),也可以用來(lái)寫(xiě)短的匯編程序,尤其對(duì)初學(xué)者而言,DEBUG 更是最佳的入門工具。因?yàn)镈EBUG操作容易:只要鍵入DEBUG回車,A回車即可進(jìn)行匯編,過(guò)程簡(jiǎn)單,而使用編譯器時(shí),必須用到文本編輯器、編譯器本身、LINK以及EXE2BIN等程序,其中每一個(gè)程序都必須用到一系列相當(dāng)復(fù)雜的命令才能工作,而且用編譯器處理源程序,必須加入許多與指令語(yǔ)句無(wú)關(guān)的指示性語(yǔ)句,以供編譯器識(shí)別,使用 DEBUG 可以避免一開(kāi)始就碰到許多難以理解的程序行。DEBUG 除了能夠匯編程序之外,還可用來(lái)檢查和修改內(nèi)存位置、載入儲(chǔ)存和執(zhí)行程序、以及檢查和修改寄存器,換句話說(shuō),DEBUG是為了讓我們接觸硬件而設(shè)計(jì)的。(8086常用指令用法將在每個(gè)匯編程序中講解,限于篇幅,不可能將所有指令列出)。
DEBUG的的A命令可以匯編出簡(jiǎn)單的COM文件,所以DEBUG編寫(xiě)的程序一定要由地址 100h(COM文件要求)開(kāi)始才合法。FOLLOW ME,SETP BY SETP(步步回車):
輸入 A100 ; 從DS:100開(kāi)始匯編
2.輸入 MOV DL,1 ; 將數(shù)值 01h 裝入 DL 寄存器
3.輸入 MOV AH,2 ; 將數(shù)值 02h 裝入 DL 寄存器
4.輸入 INT 21 ; 調(diào)用DOS 21號(hào)中斷2號(hào)功能,用來(lái)逐個(gè)顯示裝入DL的字符
5.輸入 INT 20 ; 調(diào)用DOS 20號(hào)中斷,終止程序,將控制權(quán)交回給 DEBUG
6.請(qǐng)按 Enter 鍵
7.現(xiàn)在已將匯編語(yǔ)言程序放入內(nèi)存中了,輸入 G(運(yùn)行)
8.出現(xiàn)結(jié)果:輸出一個(gè)符號(hào)。
ㄖ ←輸出結(jié)果其實(shí)不是它,因WORD97無(wú)法顯示原結(jié)果,故找一贗品將就著。
Program terminated normally
我們可以用U命令將十六進(jìn)制的機(jī)器碼反匯編(Unassemble)成匯編指令。你將發(fā)現(xiàn)每一行右邊的匯編指令就是被匯編成相應(yīng)的機(jī)器碼,而8086實(shí)際上就是以機(jī)器碼來(lái)執(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命令來(lái)查看、改變寄存器內(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開(kāi)始執(zhí)行,那么終止程序時(shí),DEBUG會(huì)自動(dòng)將IP內(nèi)容重新設(shè)定為100。當(dāng)你要將此程序做成一個(gè)獨(dú)立的可執(zhí)行文件,則可以用N命令對(duì)該程序命名。但一定要為COM文件,否則無(wú)法以DEBUG載入。
輸入N SMILE.COM ;我們得告訴DEBUG程序長(zhǎng)度:程序從100開(kāi)始到106,故占用7
;字節(jié)。我們利用BX存放長(zhǎng)度值高位部分,而以CX存放低位部分。
2.輸入RBX ;查看 BX 寄存器的內(nèi)容,本程序只有7個(gè)字節(jié),故本步可省略
3.輸入 RCX ;查看 CX 寄存器的內(nèi)容
4.輸入 7 ;程序的字節(jié)數(shù)
5.輸入 W ;用W命令將該程序?qū)懭耄╓rite)磁盤(pán)中
修行至此,我們便可以真正接觸8086匯編指令了。 當(dāng)我們寫(xiě)匯編語(yǔ)言程序的時(shí)候,通常不會(huì)直接將機(jī)器碼放入內(nèi)存中,而是打入一串助記符號(hào)(Mnemonic Symbols),這些符號(hào)比十六進(jìn)制機(jī)器碼更容易記住,此之謂匯編指令。助記符號(hào),告訴CPU應(yīng)執(zhí)行何種運(yùn)算。 也就是說(shuō),助憶符號(hào)所構(gòu)成的匯編語(yǔ)言是為人設(shè)計(jì)的,而機(jī)器語(yǔ)言是對(duì)PC設(shè)計(jì)的。
現(xiàn)在,我們?cè)賮?lái)剖析一個(gè)可以將所有ASCII碼顯示出來(lái)的程序。
1. 輸入 DEBUG
2. 輸入 A100
3.輸入 MOV CX,0100 ;裝入循環(huán)次數(shù)
MOV DL,00 ;裝入第一個(gè)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號(hào)中斷9H號(hào)功能。輸入下行程序,存盤(pán)并執(zhí)行看看:
1.輸入 A100
MOV DX,109 ;DS:DX = 字符串的起始地址
MOV AH,9 ;DOS的09h功能調(diào)用
INT 21 ;字符串輸出
INT 20
DB 'UNDERSTAND?$';定義字符串
在匯編語(yǔ)言中,有兩種不同的指令:1.正規(guī)指令:如 MOV 等,是屬于CPU的指令,用來(lái)告訴CPU在程序執(zhí)行時(shí)應(yīng)做些什么,所以它會(huì)以運(yùn)算碼(OP-code)的方式存入內(nèi)存中;2.偽指令:如DB等,是屬于DEBUG等編譯器的指令,用來(lái)告訴編譯器在編譯時(shí)應(yīng)做些什么。DB(Define Byte)指令用來(lái)告訴DEBUG 將單引號(hào)內(nèi)的所有ASCII 碼放入內(nèi)存中。使用 9H 功能的字符串必須以$結(jié)尾。用D命令可用來(lái)查看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)在,我們來(lái)剖析另一個(gè)程序:由鍵盤(pán)輸入任意字符串,然后顯示出來(lái)。db 20指示DEBUG保留20h個(gè)未用的內(nèi)存空間供緩沖區(qū)使用。
輸入A100
MOV DX,0116 ;DS:DX = 緩沖區(qū)地址,由DB偽指令確定緩沖區(qū)地址
MOV AH,0A ;0Ah 號(hào)功能調(diào)用
INT 21 ;鍵盤(pán)輸入緩沖區(qū)
MOV DL,0A ;由于功能Ah在每個(gè)字符串最后加一個(gè)歸位碼(0Dh由 Enter
MOV AH,02 ;產(chǎn)生),使光標(biāo)自動(dòng)回到輸入行的最前端,為了使新輸出的
INT 21 ;字符串不會(huì)蓋掉原來(lái)輸入的字符串,所以利用功能2h加一
;個(gè)換行碼(OAh),使得光標(biāo)移到下一行的的最前端。
MOV DX,0118 ;裝入字符串的起始位置
MOV AH,09 ;9h功能遇到$符號(hào)才會(huì)停止輸出,故字符串最后必須加上
INT 21 ;$,否則9h功能會(huì)繼續(xù)將內(nèi)存中的無(wú)用數(shù)據(jù)胡亂顯示出來(lái)
INT 20
DB 20 ;定義緩沖區(qū)
送你一句話:學(xué)匯編切忌心浮氣燥。
客套話就不講了。工欲善其事,必先利其器。與其說(shuō)DEBUG 是編譯器,倒不如說(shuō)它是“直譯器”,DEBUG的A命令只可將一行匯編指令轉(zhuǎn)成機(jī)器語(yǔ)言,且立刻執(zhí)行。真正編譯器(MASM)的運(yùn)作是利用文本編輯器(EDIT等)將匯編指令建成一個(gè)獨(dú)立且附加名為.ASM的文本文件,稱源程序。它是MASM 程序的輸入部分。MASM將輸入的ASM文件,編譯成.OBJ文件,稱為目標(biāo)程序。OBJ文件僅包含有關(guān)程序各部份要載入何處及如何與其他程序合并的信息,無(wú)法直接載入內(nèi)存執(zhí)行。鏈結(jié)程序LINK則可將OBJ文件轉(zhuǎn)換成可載入內(nèi)存執(zhí)行(EXEcute)的EXE文件。還可以用EXE2BIN,將符合條件的EXE文件轉(zhuǎn)成COM文件(COM 文件不但占用的內(nèi)存最少,而且運(yùn)行速度最快)。
下面我們用MASM寫(xiě)一個(gè)與用DEBUG寫(xiě)的第一個(gè)程序功能一樣的程序。
用EDIT編輯一個(gè)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.因?yàn)镸ASM會(huì)將所有的數(shù)值假設(shè)為十進(jìn)制,而DEBUG則只使用十六進(jìn)制,所以在源程序中,我們必須在有關(guān)數(shù)字后加上代表進(jìn)制的字母,如H代表十六進(jìn)制,D代表十進(jìn)制。若是以字母開(kāi)頭的十六進(jìn)制數(shù)字,還必須在字母前加個(gè)0,以表示它是數(shù),如0AH。2.源程序增加五行敘述:prognam segment 與 prognam ends 是成對(duì)的,用來(lái)告訴 MASM 及LINK,此程序?qū)⒎旁谝粋€(gè)稱為PROGNAM(PROGram NAMe)的程序段內(nèi),其中段名(PROGNAM)可以任取,但其位置必須固定。assume cs:prognam 必須在程序的開(kāi)頭,用來(lái)告訴編譯器此程序所在段的位置放在CS寄存器中。end用來(lái)告訴MASM,程序到此結(jié)束, ORG 100H作用相當(dāng)于DEBUG的A100,從偏移量100開(kāi)始匯編。COM 文件的所有源程序都必須包含這五行,且必須依相同的次序及位置出現(xiàn),這點(diǎ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]: ←是否改動(dòng)輸出OBJ文件名,如不改就ENTER
Source listing [NUL.LST]: ← 是否需要列表文件(LST),不需要就ENTER
Cross-reference [NUL.CRF]: ←是否需要對(duì)照文件(CRF),不需要?jiǎng)tENTER
50162 + 403867 Bytes symbol space free
0 Warning Errors ←警告錯(cuò)誤,表示編譯器對(duì)某些語(yǔ)句不理解,通常是輸入錯(cuò)誤。
0 Severe Errors ←嚴(yán)重錯(cuò)誤,會(huì)造成程序無(wú)法執(zhí)行,通常是語(yǔ)法結(jié)構(gòu)錯(cuò)誤。
如果沒(méi)有一個(gè)錯(cuò)誤存在,即可生成OBJ文件。OBJ中包含的是編譯后的二進(jìn)制結(jié)果,它還無(wú)法被 DOS載入內(nèi)存中加以執(zhí)行,必須加以鏈結(jié)(Linking)。以LINK將OBJ文件(SMILE.OBJ)鏈結(jié)成 EXE 文件(SMILE.EXE)時(shí),。
1.輸入 LINK SMILE ←不用附加名OBJ
Microsoft (R) Overlay Linker Version 3.64
Copyright (C) Microsoft Corp 1981, 1988. All rights reserved.
Run File [SMILE.EXE]: ← 是否改動(dòng)輸出EXE文件名,如不改就ENTER
List File [NUL.MAP]: ← 是否需要列表文件(MAP),不需要?jiǎng)tENTER
Libraries [.LIB]: ←是否需要庫(kù)文件,要就鍵入文件名,不要?jiǎng)tENTER
LINK : warning L4021: no stack segment← 由于COM文件不使用堆棧段,所以錯(cuò)誤信息
←"no stack segment"并不影響程序正常執(zhí)行
至此已經(jīng)生成EXE文件,我們還須使用EXE2BIN 將EXE文件(SMILE.EXE),轉(zhuǎn)換成COM文件(SMILE.COM)。輸入EXE2BIN SMILE產(chǎn)生 BIN 文件(SMILE.BIN)。其實(shí) BIN 文件與 COM 文件是完全相同的,但由于DOS只認(rèn)COM、EXE及BAT文件,所以BIN文件無(wú)法被正確執(zhí)行,改名或直接輸入 EXE2BIN SMILE SMILE.COM即可。現(xiàn)在,磁盤(pán)上應(yīng)該有 SMILE.COM 文件了,你只要在提示符號(hào)C:>下,直接輸入文件名稱 SMILE ,就可以執(zhí)行這個(gè)程序了。
你是否覺(jué)得用編譯器產(chǎn)生程序的方法,比 DEBUG 麻煩多了!以小程序而言,的確是如此,但對(duì)于較大的程序,你就會(huì)發(fā)現(xiàn)其優(yōu)點(diǎn)了。我們?cè)賹SCII程序以編譯器方式再做一次,看看有無(wú)差異。首先,用EDIT.COM建立 ASCII.ASM 文件。
prognam segment ;定義段
assume cs:prognam ;把上面定義段的段基址放入 CS
mov cx,100h ; 裝入循環(huán)次數(shù)
mov dl,0 ; 裝入第一個(gè)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 ;匯編終止
在匯編語(yǔ)言的源程序中,每一個(gè)程序行都包含三項(xiàng)元素:
start: mov dl,1 ;裝入第一個(gè)ASCII碼,隨后每次循環(huán)裝入新碼
標(biāo)識(shí)符 表達(dá)式 注解
在原始文件中加上注解可使程序更易理解,便于以后參考。每行注解以“;”與程序行分離。編譯器對(duì)注解不予理會(huì),注解的數(shù)據(jù)不會(huì)出現(xiàn)在OBJ、EXE或COM文件中。由于我們?cè)趯?xiě)源程序時(shí),并不知道每一程序行的地址,所以必須以符號(hào)名稱來(lái)代表相對(duì)地址,稱為“標(biāo)識(shí)符”。我們通常在適當(dāng)行的適當(dāng)位置上,鍵入標(biāo)識(shí)符。標(biāo)識(shí)符(label)最長(zhǎng)可達(dá)31 個(gè)字節(jié),因此我們?cè)诔绦蛑校M量以簡(jiǎn)潔的文字做為標(biāo)識(shí)符。現(xiàn)在,你可以將此ASCII.ASM 文件編譯成 ASCII.COM 了。1.MASM ASCII,2.LINK ASCII,3.EXE2BIN ASCII ASCII.COM。
注意:當(dāng)你以編譯器匯編你設(shè)計(jì)的程序時(shí),常會(huì)發(fā)生打字錯(cuò)誤、標(biāo)識(shí)符名稱拼錯(cuò)、十六進(jìn)制數(shù)少了h、邏輯錯(cuò)誤等。匯編老手常給新人的忠告是:最好料到自己所寫(xiě)的程序一定會(huì)有些錯(cuò)誤(別人告訴我的);如果第一次執(zhí)行程序后,就得到期望的結(jié)果,你最好還是在檢查一遍,因?yàn)樗赡苁清e(cuò)的。原則上,只要大體的邏輯架構(gòu)正確,查找程序中錯(cuò)誤的過(guò)程,與寫(xiě)程序本身相比甚至更有意思。寫(xiě)大程序時(shí),最好能分成許多模塊,如此可使程序本身的目的較單純,易于撰寫(xiě)與查錯(cuò),另外也可讓程序中不同部份之間的界限較清楚,節(jié)省編譯的時(shí)間。如果讀程序有讀不懂的地方最好用紙筆記下有關(guān)寄存器、內(nèi)存等內(nèi)容,在紙上慢慢比劃,就豁然開(kāi)朗了。 下面我們將寫(xiě)一個(gè)能從鍵盤(pán)取得一個(gè)十進(jìn)制的數(shù)值,并將其轉(zhuǎn)換成十六進(jìn)制數(shù)值而顯示于屏幕上的“大程序”。前言:要讓8086執(zhí)行這樣的功能,我們必須先將此問(wèn)題分解成一連串的步驟,稱為程序規(guī)劃。首先,以流程圖的方式,來(lái)確保整個(gè)程序在邏輯上沒(méi)有問(wèn)題(不用說(shuō)了吧!什么語(yǔ)言都要有此步驟)。這種模塊化的規(guī)劃方式,稱之為“由上而下的程序規(guī)劃”。而在真正寫(xiě)程序時(shí),卻是從最小的單位模塊(子程序)開(kāi)始,當(dāng)每個(gè)模塊都完成之后,再合并成大程序;這種大處著眼,小處著手的方式稱為“由下而上的程序設(shè)計(jì)”。
我們的第一個(gè)模塊是BINIHEX,其主要用途是從8086的BX寄存器中取出二進(jìn)制數(shù),并以十六進(jìn)制方式顯示在屏幕上。注意:子程序如不能獨(dú)立運(yùn)行,實(shí)屬正常。
binihex segment
assume cs:binihex
mov ch,4 ;記錄轉(zhuǎn)換后的十六進(jìn)制位數(shù)(四位)
rotate: mov cl,4 ;利用CL當(dāng)計(jì)數(shù)器,記錄寄存器數(shù)位移動(dòng)次數(shù)
rol bx,cl ;循環(huán)寄存器BX的內(nèi)容,以便依序處理4個(gè)十六進(jìn)制數(shù)
mov al,bl ;把bx低八位bl內(nèi)數(shù)據(jù)轉(zhuǎn)移至al
and al,0fh ;把無(wú)用位清零
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減一,減到零時(shí),零標(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)容將由第二個(gè)子程序提供)的內(nèi)容,以便依序處理4個(gè)十六進(jìn)制數(shù):1. 利用CL當(dāng)計(jì)數(shù)器,記錄寄存器移位的次數(shù)。2.將BX的第一個(gè)十六進(jìn)制值移到最右邊。利用 AND (邏輯“與”運(yùn)算:對(duì)應(yīng)位都為1時(shí),其結(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指令會(huì)將兩個(gè)表達(dá)式相加,其結(jié)果存于左邊表達(dá)式內(nèi)。標(biāo)志寄存器(Flag Register)是一個(gè)單獨(dú)的十六位寄存器,有9個(gè)標(biāo)志位,某些匯編指令(大部份是涉及比較、算術(shù)或邏輯運(yùn)算的指令)執(zhí)行時(shí),會(huì)將相關(guān)標(biāo)志位置1或清0, 常碰到的標(biāo)志位有零標(biāo)志(ZF)、符號(hào)標(biāo)志(SF)、溢出標(biāo)志(OF)和進(jìn)位標(biāo)志(CF)。 標(biāo)志位保存了某個(gè)指令執(zhí)行后對(duì)它的影響,可用其他相關(guān)指令,查出標(biāo)志的狀態(tài),根據(jù)狀態(tài)產(chǎn)生動(dòng)作。CMP指令很像減法,是將兩個(gè)表達(dá)式的值相減,但寄存器或內(nèi)存的內(nèi)容并未改變,只是相對(duì)的標(biāo)志位發(fā)生改變而已:若 AL 值小于 3Ah,則正負(fù)號(hào)標(biāo)志位會(huì)置0,反之則置1。 JL指令可解釋為:小于就轉(zhuǎn)移到指定位置,大于、等于則向下執(zhí)行。CMP和JG 、JL等條件轉(zhuǎn)移指令一起使用,可以形成程序的分支結(jié)構(gòu),是寫(xiě)匯編程序常用技巧。
第二個(gè)模塊DECIBIN 用來(lái)接收鍵盤(pán)打入的十進(jìn)制數(shù),并將它轉(zhuǎn)換成二進(jìn)制數(shù)放于BX 寄存器中,供模塊1 BINIHEX使用。
decibin segment
assume cs:decibin
mov bx,0 ;BX清零
newchar:mov ah,1 ;
int 21h ;讀一個(gè)鍵盤(pán)輸入符號(hào)入al,并顯示
sub al,30h ;al減去30H,結(jié)果存于al中,完成ASCII碼轉(zhuǎn)二進(jì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 ;十進(jìn)制數(shù)10入cx
mul cx ;表達(dá)式的值與ax內(nèi)容相乘,并將結(jié)果存于ax
xchg ax,bx
add bx,ax
jmp newchar ;無(wú)條件轉(zhuǎn)移
exit: int 20 ;回主程序
decibin ends
end
CBW 實(shí)際結(jié)果是:若AL中的值為正,則AH填入00h;反之,則AH填入FFh。XCHG常用于需要暫時(shí)保留某個(gè)寄存器中的內(nèi)容時(shí)。
當(dāng)然,還得一個(gè)子程序(CRLF)使后顯示的十六進(jìn)制數(shù)不會(huì)蓋掉先輸入的十進(jìn)制數(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等模塊合并成一個(gè)大程序了。首先,我們要將這三個(gè)模塊子程序略加改動(dòng)。然后,再寫(xiě)一段程序來(lái)調(diào)用每一個(gè)子程序。
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也是成對(duì)出現(xiàn),用來(lái)識(shí)別并定義一個(gè)程序。其實(shí),PROC 真正的作用只是告訴編譯器:所調(diào)用的程序是屬于近程(NEAR)或遠(yuǎn)程(FAR)。 一般的程序是由 DEBUG 直接調(diào)用的,所以用 INT 20 返回,用 CALL 指令所調(diào)用的程序則改用返回指令RET,RET會(huì)把控制權(quán)轉(zhuǎn)移到棧頂所指的地址,而該地址是由調(diào)用此程序的 CALL指令所放入的。
各模塊都搞定了,然后我們把子程序組合起來(lái)就大功告成
decihex segment ;主程序
assume cs:decihex
org 100h
mov cx,4 ;循環(huán)次數(shù)入cx;由于子程序要用到cx,故子程序要將cx入棧
repeat: call decibin;調(diào)用十進(jìn)制轉(zhuǎn)二進(jìn)制子程序
call crlf ;調(diào)用添加回、換行符子程序
call binihex ;調(diào)用二進(jìn)制轉(zhuǎn)十六進(jìn)制并顯示子程序
call crlf
loop repeat ;循環(huán)4次,可連續(xù)運(yùn)算4次
mov ah,4ch ; 調(diào)用DOS21號(hào)中斷4c號(hào)功能,退出程序,作用跟INT 20H
int 21H ; 一樣,但適用面更廣,INT20H退不出時(shí),試一下它
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指令用來(lái)調(diào)用子程序,并將控制權(quán)轉(zhuǎn)移到子程序地址,同時(shí)將CALL的下行一指令地址定為返回地址,并壓入堆棧中。CALL 可分為近程(NEAR)及遠(yuǎn)程(FAR)兩種:1.NEAR:IP的內(nèi)容被壓入堆棧中,用于程序與程序在同一段中。2.FAR:CS 、IP寄存器的內(nèi)容依次壓入堆棧中,用于程序與程序在不同段中。PUSH、POP又是一對(duì)指令用于將寄存器內(nèi)容壓入、彈出,用來(lái)保護(hù)寄存器數(shù)據(jù),子程序調(diào)用中運(yùn)用較多。堆棧指針有個(gè)“后進(jìn)先出”原則,像PUSH AX,PUSH BX…POP BX,POP AX這樣才能作到保護(hù)數(shù)據(jù)絲毫不差。
匯編語(yǔ)言超濃縮教程到這要告一段落了,希望能奠定你獨(dú)立設(shè)計(jì)的基礎(chǔ)。而更多更好的技巧則全依賴你平時(shí)的積累了。祝你成功!
??? 在學(xué)匯編時(shí),很多初學(xué)者對(duì)PC的尋址方式和很不理解,甚至是很難理解。的確,這方面的知識(shí)是很抽象的,需要比較強(qiáng)的空間想象能力。尤其是我們?cè)谳斎胱址畷r(shí),那這些字符是如何進(jìn)行排列的呢?對(duì)于,這個(gè)問(wèn)題,我相信很多初學(xué)者也是很難想象是如何排列。但是,我可以這樣比喻:內(nèi)存就是有很多棟“樓房”,“樓房”又是由“單元號(hào)”,“門戶號(hào)”組成,那“樓房”就相當(dāng)于內(nèi)存地址的段地址,“單元號(hào)”就相當(dāng)于內(nèi)存的的 偏移地址,“門戶號(hào)(家)”就相當(dāng)于“變地址”,而每個(gè)單元有16個(gè)"門戶號(hào)(家)",又當(dāng)我們找到"門戶號(hào)(家)"后,走進(jìn)這個(gè)"門戶號(hào)(家)"就會(huì)見(jiàn)到里面會(huì)有"人",而我們所說(shuō)的人就是寄存器所指的"內(nèi)容"了,我畫(huà)個(gè)圖給你們看就會(huì)一目了然了。
用DEBUG的D命令得出這樣的效果:
|---------->0B1F就是"樓房"------>段地址
|
|???? |------>右邊的就是"單元號(hào)"--->偏移地址
|???? |
|???? |??????????? |-------->這部分就是"門戶號(hào)"----->變地址
|???? |????????? |<------------------------------------------>|
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
'
'
'
[省略]
看完這個(gè)圖之后,是不是就很明了呢?但是聰明的人就會(huì)有疑問(wèn),那我們?cè)趺醋哌M(jìn)"門戶號(hào)(家)"呢?問(wèn)得好,所以了為了可以走進(jìn)"門戶號(hào)(家)",就出現(xiàn)了一個(gè)叫做"尋址方式"的概念!說(shuō)白了,就是教你如何找到這個(gè)"門戶號(hào)(家)".呵呵!
好現(xiàn)在都明白了嗎?那你們就看看我是怎么理解PC的尋址方式(通俗易懂):
在這我就只介紹比較難理解的:
1:寄存器直接尋址:
你就想成:其實(shí)你已經(jīng)站在你要找的"門戶號(hào)(家)"面前了,直接敲門進(jìn)去就OK了!
例子: MOV AX,[2000H]
MOV AX,2000H -->2000H為存放操作數(shù)單元號(hào)的符號(hào)地址
上面兩者是不等效的
2:寄存器間接尋址方式:
你就想成:你已經(jīng)站在你要找的"門戶號(hào)(家)"的"單元號(hào)",你要找到它,必須知道它在當(dāng)前"單元號(hào)"幾樓.假如它在6樓,那你就上到6樓就OK了!!注意,最高只有16樓,因?yàn)槭裁茨?那就用DEBUG的D命令看看呀,慢慢數(shù)哦,呵呵!!
例子: MOV AX,[BX]
計(jì)算公式: 物理地址=16d*(DS)+(BX)
物理地址=16d*(DS)+(SI)
物理地址=16d*(DS)+(DI)
物理地址=16d*(SS)+(BP)
3:寄存器相對(duì)尋址方式:
你就想成:你要找的"門戶號(hào)(家)"其實(shí)就在你家的樓上或者樓下,你要找到它,就 必須知道它在你樓上幾樓,或者在樓下幾樓!就OK了!
例子: MOV AX,COUNT[SI]
MOV AX,[COUNT+SI]
其中 COUNT為位移量的符號(hào)地址
計(jì)算公式: 物理地址=16d*(DS)+(BX)+8位位移量
或+(SI) 或 16位位偏移量
或+(DI)
物理地址=16d*(SS)+(BP)+8位偏移量
4:基址變址尋址方式:
你就想成:你要找的"門戶號(hào)(家)"是跟住在同一棟樓的不同"單元號(hào)",你要找到它,就必須知道它是該棟的哪個(gè)"單元號(hào)",并且住在幾樓!那樣你就可以找到它了 !
例子: MOV AX,[BX][DI]
MOV AX,[BX+DI]
計(jì)算公式: 物理地址=16d*(DS)+(BX)+(SI)
或+(DI)
物理地址=16d*(SS)+(BP)+(SI)
或+(DI)
5:相對(duì)基址變址尋址方式:
你就想成:你就想成:你要找的"門戶號(hào)(家)"是跟住在同一棟樓的不同"單元號(hào)",它比你高幾層樓或者低幾層樓,然后用的你目前的樓數(shù)+/-就可以得出你要找的住在幾樓了!
例子: MOV,AX,MASK[BX][SI]
MOV,AX,MASK[BX+SI]
MOV,AX,[MASK+BX+SI]
以上三個(gè)例子是等效的!!
計(jì)算公式: 物理地址=16d*(DS)+(BX)+(SI)+8位位移量
或+(DI) 或 16位位偏移量
物理地址=16d*(SS)+(BP)+(SI)+8位位移量
或+(DI) 或 16位位偏移量
---------------------------------------------------------------------
呵呵,終于寫(xiě)完了這篇教程,好累哦!! 是不是覺(jué)得我的思維很另類呀,要?jiǎng)?chuàng)新呀!
書(shū)上太理論了,我就創(chuàng)新一個(gè),不知道你們看得懂嗎?
呵呵,反正你們不要!@##)(#$*!@(@我就行了,我很努力寫(xiě)了!!!
下面,我舉個(gè)程序例子,讓你們加深印象!!!
----------------------------------------------------------------------
編程步驟:
1: 建立緩沖區(qū),為輸入字符串(最多能輸入9個(gè))
2: 取緩沖區(qū)的首地址,以便后面進(jìn)行"寄存器間接尋址方式"
3: 利用"寄存器間接尋址方式"取得實(shí)際輸入字符個(gè)數(shù),以便確認(rèn)循環(huán)次數(shù)
4: 利用"寄存器間接尋址方式"輸入字符串的最后一個(gè)字符
5: 利用LOOP指令和2號(hào)顯示功能來(lái)進(jìn)行倒著顯示
----------------------------------------------------------------------
;程序功能:任意輸入幾個(gè)字符(最多能輸入9個(gè)),按回車則倒著輸出!
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] ;看這個(gè)就是"寄存器間接尋址方式"
xor ch,ch ;其目的就是取實(shí)際輸入字符個(gè)數(shù)
mov di,cx
lop: mov ah,2
mov dx,[bx+di+1];看這又是"寄存器間接尋址方式"
int 21h ;其目的就是取輸入字符串的最后一個(gè)字符
dec di
loop lop ;依次循環(huán)倒著輸出字符
mov ah,4ch
int 21h
code ends
end start
-----------------------------------------------------------------------
完工了
如果前面的編譯過(guò)程沒(méi)有錯(cuò)誤的話,現(xiàn)在我們應(yīng)該已經(jīng)得到了一個(gè)HelloWDM.sys文件,假設(shè)它是放在D:\HelloWDM\objfre\i386中。
安裝WDM驅(qū)動(dòng)程序可以用兩種方法,一種是利用注冊(cè)表,還有一種是利用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="???" |
注意它可以同時(shí)在Win98或者Win2000中使用(系統(tǒng)會(huì)通過(guò)這個(gè)INF文件里面的字段名稱,自動(dòng)選擇適合當(dāng)前系統(tǒng)的安裝方法的)。關(guān)于INF文件的各個(gè)字段含義現(xiàn)在我也不知道,所以也沒(méi)有辦法說(shuō)清楚,如果誰(shuí)看到這篇文章,而又知道的話,不妨為我一份。
準(zhǔn)備好這個(gè) HelloWDM.INF 文件后,讓我們打開(kāi)控制面板,雙擊“添加/刪除硬件”,選擇“添加/排除設(shè)備故障”->“添加新設(shè)備”->“否,我想從列表選擇硬件”->“其它設(shè)備”->“從磁盤(pán)安裝”,選擇 HelloWDM.INF 所在的路徑,然后安裝。
當(dāng)安裝完成后,系統(tǒng)就會(huì)添加上你寫(xiě)好的驅(qū)動(dòng)程序了。(可以在“設(shè)備管理器”中查看到)。然后重啟電腦,這個(gè)驅(qū)動(dòng)程序就投入使用啦。
關(guān)于安裝,我也只知道這么多,到底安裝驅(qū)動(dòng)程序時(shí),操作系統(tǒng)都作了些什么,我也不是很清楚,等我弄明白了我再貼上。
DDK分為98 DDK和2000 DDK兩種,它們工作起來(lái)是大同小異的,不過(guò)有些驅(qū)動(dòng)程序只能在2000 DDK中使用。由于Win98注定是一種即將被淘汰的操作系統(tǒng)了,所以我學(xué)習(xí)的時(shí)候也沒(méi)有過(guò)多的關(guān)注,我用的是2000的DDK,所以以下的所有內(nèi)容都是針對(duì)2000 DDK的。
·準(zhǔn)備工作
1、確定你已經(jīng)安裝了Visual C++
2、安裝2000 DDK
3、安裝2000 DDK成功后,在“開(kāi)始”->“程序”里應(yīng)該有“Development Kits”->“Windows 2000 DDK”的項(xiàng)目。
(注意一定要先安裝好VC,然后才安裝DDK,這個(gè)順序決不能顛倒!!)
4、保證DDKROOT環(huán)境變量設(shè)置為Windows 2000 DDK的基目錄,如果不是的話,請(qǐng)?jiān)诳刂泼姘濉?B>系統(tǒng)”屬性的“高級(jí)”標(biāo)簽環(huán)境變量編輯器中設(shè)置好這個(gè)環(huán)境變量。
·編寫(xiě)必需的文件
編譯WDM程序的時(shí)候,有兩個(gè)文件是必須要有的,它們是:
1、makefile
(這個(gè)是什么啊?你可能會(huì)問(wèn)。)對(duì)于比較年輕的程序員來(lái)說(shuō),有可能沒(méi)有見(jiàn)過(guò)這個(gè)文件吧。其實(shí)在VC這些IDE出現(xiàn)之前,我們都必須使用makefile來(lái)確定項(xiàng)目中哪些文件需要重新編譯,現(xiàn)在的IDE都把這個(gè)工作自動(dòng)做好了
我們要做的工作很簡(jiǎn)單,就是提供這樣一個(gè)文件,它的內(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 //編譯出來(lái)的驅(qū)動(dòng)程序的名稱 TARGETTYPE=DRIVER //編譯的類型是驅(qū)動(dòng)程序編譯 DRIVERTYPE=WDM //驅(qū)動(dòng)程序的類型是WDM驅(qū)動(dòng)程序 TARGETPATH=OBJ //生成的文件存放在OBJ目錄中 INCLUDES=$(BASEDIR)\inc;\ //這是需要引入的頭文件 $(BASEDIR)\inc\ddk;\ TARGETLIBS=$(BASEDIR)\lib\*\free\usbd.lib\ //這是需要引入的庫(kù)文件 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文件(實(shí)際上你會(huì)發(fā)現(xiàn),幾乎所有的WDM驅(qū)動(dòng)程序都會(huì)調(diào)用NTOSKRNL.EXE文件,從它的名字你可以看出為什么了吧?),并且輸出了一個(gè)函數(shù)“Vcomm_DriverControl”。這表明,其實(shí).sys跟.exe文件一樣,都是一種PE文件來(lái)的。不同的是,.sys文件Import的通常是NTOSKRNL.EXE,而.exe文件Import的通常是KERNEL32.DLL和USER32.DLL。
知道了這些有什么用呢?實(shí)際上,由于.sys通常不調(diào)用KERNEL32.DLL和USER32.DLL,所以你是不能在設(shè)備驅(qū)動(dòng)程序里面調(diào)用任何C、C++和Win32函數(shù)的,而且也不能用C++關(guān)鍵字new和delete等(可以用malloc和free來(lái)代替),而必須使用大量的內(nèi)核函數(shù)。另外,你應(yīng)該也能看到她調(diào)用了像IoDeleteDevice、IoAttachDeviceToDeviceStack等等函數(shù),這些你以前可能沒(méi)有見(jiàn)過(guò)的函數(shù)都是些內(nèi)核函數(shù)。為了讀者的方便,下面我列出一些常見(jiàn)的驅(qū)動(dòng)程序可用的內(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… 對(duì)象管理器 Po… 電源管理 Ps… 進(jìn)程結(jié)構(gòu) Rtl… 運(yùn)行時(shí)庫(kù) Se… 安全引用監(jiān)視 Zw… 其他函數(shù) |
最后讓我們?cè)賮?lái)看看,寫(xiě)設(shè)備驅(qū)動(dòng)程序時(shí)必須注意的一些問(wèn)題:
1、內(nèi)核宏
如果查看DDK頭文件,會(huì)發(fā)現(xiàn)有幾個(gè)內(nèi)核函數(shù)是以宏的方式實(shí)現(xiàn)的。這種宏中有幾個(gè)宏的定義是相當(dāng)糟糕的。例如,我們看到RemoveHeadList的定義如下:
| #define RemoveHeadList(ListHead) (ListHead)->Flink; {RemoveEntryList((ListHead)->Flink)} |
如果以以下方式調(diào)用RemoveHeadList,則將編譯錯(cuò)誤的代碼:
| if(SomethingInList) Entry = RemoveHeadList(list); |
使這個(gè)調(diào)用安全的唯一方法是使用花括號(hào):
| if(SomethingInList) { Entry = RemoveHeadList(list); } |
所以我們切勿為了貪圖一時(shí)的方便,而使用不太規(guī)范的寫(xiě)法,最好是在所有的if、for和while等語(yǔ)句中使用花括號(hào)。
2、驅(qū)動(dòng)程序函數(shù)名稱
跟C/C++的main()函數(shù)一樣,設(shè)備驅(qū)動(dòng)程序也有一個(gè)必須存在,而且只能以DriverEntry()為名稱的入口函數(shù)。然而,除此之外,我們可以使用任何名字來(lái)給其他函數(shù)命名——只要你自己記得就行了,當(dāng)然,最好符合某些特定的規(guī)范啦,例如匈牙利命名法……
3、安裝時(shí)的問(wèn)題
·在Windows98中驅(qū)動(dòng)程序可執(zhí)行文件必須是8.3文件名。(別問(wèn)我為什么,我也不知道,我只能建議你去問(wèn)比爾該死)
·如果INF文件中含有非法節(jié)的詳細(xì)資料,Windows將不使用這個(gè)INF文件。
本節(jié)羅羅嗦嗦講了一大堆,跟實(shí)際的編程卻并沒(méi)有太大的關(guān)系,前傳嘛!就是這樣的啦!