|
2009年2月16日
void WriteBMPFile(HBITMAP bitmap, LPTSTR filename, HDC hDC) { BITMAP bmp; PBITMAPINFO pbmi; WORD cClrBits; HANDLE hf; // file handle BITMAPFILEHEADER hdr; // bitmap file-header PBITMAPINFOHEADER pbih; // bitmap info-header LPBYTE lpBits; // memory pointer DWORD dwTotal; // total count of bytes DWORD cb; // incremental count of bytes BYTE *hp; // byte pointer DWORD dwTmp;
// create the bitmapinfo header information
if (!GetObject( (bitmap, sizeof(BITMAP), (LPSTR)&bmp)){ AfxMessageBox("Could not retrieve bitmap info"); return; }
// Convert the color format to a count of bits. cClrBits = (WORD)(bmp.bmPlanes * bmp.bmBitsPixel); if (cClrBits == 1) cClrBits = 1; else if (cClrBits <= 4) cClrBits = 4; else if (cClrBits <= 8) cClrBits = 8; else if (cClrBits <= 16) cClrBits = 16; else if (cClrBits <= 24) cClrBits = 24; else cClrBits = 32; // Allocate memory for the BITMAPINFO structure. if (cClrBits != 24) pbmi = (PBITMAPINFO) LocalAlloc(LPTR, sizeof(BITMAPINFOHEADER) + sizeof(RGBQUAD) * (1<< cClrBits)); else pbmi = (PBITMAPINFO) LocalAlloc(LPTR, sizeof(BITMAPINFOHEADER));
// Initialize the fields in the BITMAPINFO structure.
pbmi->bmiHeader.biSize = sizeof(BITMAPINFOHEADER); pbmi->bmiHeader.biWidth = bmp.bmWidth; pbmi->bmiHeader.biHeight = bmp.bmHeight; pbmi->bmiHeader.biPlanes = bmp.bmPlanes; pbmi->bmiHeader.biBitCount = bmp.bmBitsPixel; if (cClrBits < 24) pbmi->bmiHeader.biClrUsed = (1<<cClrBits);
// If the bitmap is not compressed, set the BI_RGB flag. pbmi->bmiHeader.biCompression = BI_RGB;
// Compute the number of bytes in the array of color // indices and store the result in biSizeImage. pbmi->bmiHeader.biSizeImage = (pbmi->bmiHeader.biWidth + 7) /8 * pbmi->bmiHeader.biHeight * cClrBits; // Set biClrImportant to 0, indicating that all of the // device colors are important. pbmi->bmiHeader.biClrImportant = 0;
// now open file and save the data pbih = (PBITMAPINFOHEADER) pbmi; lpBits = (LPBYTE) GlobalAlloc(GMEM_FIXED, pbih->biSizeImage);
if (!lpBits) { AfxMessageBox("writeBMP::Could not allocate memory"); return; }
// Retrieve the color table (RGBQUAD array) and the bits if (!GetDIBits(hDC, HBITMAP(bitmap), 0, (WORD) pbih->biHeight, lpBits, pbmi, DIB_RGB_COLORS)) { AfxMessageBox("writeBMP::GetDIB error"); return; }
// Create the .BMP file. hf = CreateFile(filename, GENERIC_READ | GENERIC_WRITE, (DWORD) 0, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, (HANDLE) NULL); if (hf == INVALID_HANDLE_VALUE){ AfxMessageBox("Could not create file for writing"); return; } hdr.bfType = 0x4d42; // 0x42 = "B" 0x4d = "M" // Compute the size of the entire file. hdr.bfSize = (DWORD) (sizeof(BITMAPFILEHEADER) + pbih->biSize + pbih->biClrUsed * sizeof(RGBQUAD) + pbih->biSizeImage); hdr.bfReserved1 = 0; hdr.bfReserved2 = 0;
// Compute the offset to the array of color indices. hdr.bfOffBits = (DWORD) sizeof(BITMAPFILEHEADER) + pbih->biSize + pbih->biClrUsed * sizeof (RGBQUAD);
// Copy the BITMAPFILEHEADER into the .BMP file. if (!WriteFile(hf, (LPVOID) &hdr, sizeof(BITMAPFILEHEADER), (LPDWORD) &dwTmp, NULL)) { AfxMessageBox("Could not write in to file"); return; }
// Copy the BITMAPINFOHEADER and RGBQUAD array into the file. if (!WriteFile(hf, (LPVOID) pbih, sizeof(BITMAPINFOHEADER) + pbih->biClrUsed * sizeof (RGBQUAD), (LPDWORD) &dwTmp, ( NULL))){ AfxMessageBox("Could not write in to file"); return; }
// Copy the array of color indices into the .BMP file. dwTotal = cb = pbih->biSizeImage; hp = lpBits; if (!WriteFile(hf, (LPSTR) hp, (int) cb, (LPDWORD) &dwTmp,NULL)){ AfxMessageBox("Could not write in to file"); return; }
// Close the .BMP file. if (!CloseHandle(hf)){ AfxMessageBox("Could not close file"); return; }
// Free memory. GlobalFree((HGLOBAL)lpBits); }
2009年1月9日
上次給大家介紹了Visual C++ 2008 的Feature Pack的界面庫新特性。今天給大家介紹一下,怎樣用Feature Pack把您現有的Visual C++ 程序界面修改得漂亮些。
所需的修改環境:
Visual C++ 2008 (Team Suite版Express版都可以,但必須是英文版,否則Feature Pack不支持)
正確的安裝了Visual C++ 2008 Feature Pack beta
您要修改的Visual C++ 的程序源代碼工程
具備了以上三點就可以開始進行修改操作了。但是有一點提請注意,那就是您程序中是否用到了MS C++ 9.0
編譯器不再支持的語法特性?如果有,那很不幸,我個人不推薦您升級您的程序界面,畢竟程序運行的穩定性才是最重要的。為了漂亮的界面修改已經測試過并穩定
運行的代碼,可不是一個明智的選擇。
下面我就用一個Visual C++的入門Demo Scribble 來修改。這個Scribble您可以在MSDN網站上下載到,但請您注意,我給出的這個下載工程是VS2005 for x64的。下載后,您需要進行以下改動:
1) 將Scribble工程屬性中,C/C++編譯器的Treat Warnings as error 關閉,否則您的工程將會因為一個Warning沒有解決,導致整個程序編譯失敗。如圖:
2) 編譯時,將Target 改為Win32;
羅嗦了這么多,開始修改吧!
第一步: 請確定CScribbleApp::InitialInstance() 方法中已經調用了AfxOleInit();
第二步: 在stdafx.h文件中加入 #include"afxcontrolbars.h" ,這頭文件包含了Feature Pack新增的界面類聲明;
第三步:修改CScribbleApp類繼承的父類,由CWinApp改為CWinAppEx;這個CWinAppEx類比CWinApp添
加了很多的功能,說個簡單的,CWinAppEx提供了一個SetRegistryBase方法,這個方法可以用來設定當前App所使用的注冊表的根。
第四步:修改主框架類,將CMainFrame的父類由CMDIFrameWnd改為CMDIFrameWndEx;這個修改設計到類聲明、
IMPLEMENT_DYNAMIC宏、MESSAGE_MAP宏、OnCreate函數等調用到靜態方法的地方、以及其它等等。最好是直接用
Replace all文本替換掉;
第五步:將CMDIChildWnd類替換為CMDIChildWndEx,主框架換了,子窗體也要換;
第六步:替換CTooBar為CMFCTooBar,替換CStatusBar為CMFCStatusBar;就是修改一下m_wndStatusBar和m_wndToolBar兩個變量的聲明處;
第七步:替換CMainFrame::OnCreate()函數中m_wndToolBar
和m_wndStatusBar停靠的相關代碼;將Set/Get BarStyle改為Set/Get PaneStyle
也是文本替換一下,很簡單。將DockControlBar(&m_wndStatusBar);改為
DockPane(&m_wndStatusBar);
完成以上七步,基本改造就算完成了。但是如果您現在編譯您的程序,您會發現Scribble界面基本上沒有任何改變。下面的才是更重要的,我們要添加RibbonBar了:
第一步:在CMainFrame類中聲明一個CMFCRibbonBar類型的變量m_wndRibbonBar。這個變量就代表Office2007界面里面那個替代了菜單的東東;
第二步:在CMainFrame類中聲明一個CMFCRibbonApplicationButton的變量m_MainButton。這個變量代表了Office2007界面左上角那個Home按鈕;
第三步:在CMainFrame::OnCreate函數中添加代碼。首先是創建RibbonBar對象,老規矩:
if(!m_wndRibbonBar.Create(this))
{
return -1;
}
第四步:設定m_MainButton對象:
m_MainButton.SetImage(……); //設定圖標
m_MainButton.SetToolTipText(……); //設定提示文本
m_MainButton.SetText(……); //設定按鈕文本
第五步:在CMainFrame::OnCreate函數中添加代碼,通過RibbonBar對象添加一個Category:
CMFCRibbonMainPanel *pMainPanel = m_wndRibbonBar.AddMainCategory(_T("File"));
這實際上就類似于創建了一個名為File的主菜單項;
第六步:給這個Panel添加按鈕(其實就是子菜單項):
pMainPanel->Add(new CMFCRibbonButton(ID_FILE_OPEN,_T("打開")));
……
第七步:
在CMainFrame::OnCreate()函數的最后部分,添加代碼設定當前界面的風格:
CMFCVisualManager::SetDefaultManager(RUNTIME_CLASS(CMFCVisualManagerOffice2007));
CMFCVisualManagerOffice2007::SetStyle(CMFCVisualManagerOffice2007::Office2007Luna_Blue);
代碼的第一行用來設定可視化管理器為Office2007類型,可供選擇的還有OfficXP、Office2003、VS2005
三種,換句話說,我們可以將我們的程序界面修改為Office2007、Office2003、OfficeXP、VS2005四種風格;
代碼的第二行用來設定Office2007界面的色調;
完成以上步驟后,就可以編譯運行啦,看看界面是不是改變了呢?
修改前:
修改后:
我沒有找到比較好看的圖標,也沒有把菜單都實現出來,僅僅是作為一個演示。相信在美工的幫助下,我們的MFC程序界面一定會漂亮起來的。
說到最后,我要提醒大家一下,發布程序前,已經要靜態鏈接MFC的庫。在現在VC8.0
的RTM尚不普及的情況下,就別指望您的用戶安裝部署了支持Feature
Packe的FTM庫了。粗粗看了一下,一個用向導生成的支持Feature Pack的Application(是的,安裝了Feature
Pack在用AppWizard生新程序的時候,就可以指定Feature
Pack支持了),什么代碼都不加,靜態編譯一般在6M-8M之間(還算可以接受的說)。
2008年12月31日
2008年12月24日
開發優秀的驅動程序
作為驅動開發工程師,我們需要在每一行代碼上下功夫,因為驅動程序的效率直接影響著系統的性能.而新手往往不會注意到這些細節。以為功能實現以后就萬事大吉了,其實不然,好的驅動程序不只是能實現預期的功能。它同樣需要高的效率與規范的風格。用戶花錢買我們系統是給他/她做事的,而不是給我們做測試的,所以我們要盡可能提高效率。同時好的代碼風格能大大降低我們自己的維護成本。
高效率看似容易,但要注意到每個細節還是挺難的,我們可以從以下幾點去注意這個問題:
1, 不要使用無關的代碼,這點容易理解,尤其是調試代碼,RELEASE時一定要去除這些代碼。
2, 去掉多余的函數調用,盡可能的保存一些數據。即使是最快的函數,調用它時也會引發壓棧與出棧,所以要盡量少做函數調用。當然如果一個函數返回的數據比較大,保存那些數據將占用比較多的內在空間,保存返回值就得不償失了。比如,看到有的人每次在使用一個地址時就調用MmmapIoSpace將這個地址映射到程序地址空間,用完以后又立即Unmap這個地址,下次使用時又做MAP,這就是一種及不好的方法,每次需要多調用兩個系統函數。
3.如果可行,不要在循環中使用條件判斷,尤其在一個次數很多的循環中更應該如此。
比如:
For( i=0; i<1000;>
If( m==1) ..
Else if (m==2 )….
Else …..
}
這種代碼,我們可以把If 寫在for 之外,即,每一種不同的條件寫一個循環體。
If( m==1) for ...
Else if (m==2 ) for ….
Else for …..
標簽: 程序技巧
開發DMA驅動
使用DMA的好處就是它不需要CPU的干預而直接服務外設,這樣CPU就可以去處理別的事務,從而提高系統的效率,對于慢速設備,如UART,其作用只是降低CPU的使用率,但對于高速設備,如硬盤,它不只是降低CPU的使用率,而且能大大提高硬件設備的吞吐量。因為對于這種設備,CPU直接供應數據的速度太低。
因CPU只能一個總線周期最多存取一次總線,而且對于ARM,它不能把內存中A地址的值直接搬到B地址。它只能先把A地址的值搬到一個寄存器,然后再從這個寄存器搬到B地址。也就是說,對于ARM,要花費兩個總線周期才能將A地址的值送到B地址。而DMA就不同了,一般系統中的DMA都有突發(Burst)傳輸的能力,在這種模式下,DMA能一次傳輸幾個甚至幾十個字節的數據,所以使用DMA能使設備的吞吐能力大為增強。
使用DMA時我們必須要注意如下事實:
1. DMA使用物理地址,程序是使用虛擬地址的,所以配置DMA時必須將虛擬地址轉化成物理地址。
2. 因為程序使用虛擬地址,而且一般使用CACHED地址,所以虛擬地址中的內容與其物理地址上的內容不一定一致辭,所以在啟動DMA傳輸之前一定要將該地址的CACHE刷新,即寫入內存。
3. OS并不能保證每次分配到的內在空間在物理上是連續的。尤其是在系統使用過一段時間而又分配了一塊比較大的內存時。
所以每次都需要判斷地址是不是連續的,如果不連續就需要把這段內存分成幾段讓DMA完成傳輸。
標簽: BaseKnowledge
WINCE下USBFN驅動程序的一些概念
USBFN,即USB客戶端驅動,用來將一個WINCE設備模擬成一定的USB設備,讓主機端(如PC)訪問。目前WINCE提供的USB客戶端有存儲設備,串口設備,及RNDIS網絡接口設備。
存儲設備用來將WINCE設備上的存儲空間,例如FLASH,當作一塊存儲介質給主機訪問,即將WINCE設備模擬成一個U盤。
串口設備將設備與主機的USB連線模擬成串口,WINCE和主機端都認為它們之前連接上了一根串口線,它們之間可以做串口通信,典型的應用是用來實現WINCE與PC機的同步連接。
RNDIS設備使兩端認為它們之間建立了網絡連接,通過注冊表設置可以讓主機通過WINCE設備上網或者使WINCE設備通過主機上網。
WINCE已經提供了以上三種設備的驅動程序,在同一時刻只能使用一個設備。而我們需要做的只是提供USBFN總線控制器的驅動程序。USBFN系統各個模塊的關系如下:
USBFN總路線控制器作為一個總線驅動程序,被設備管理器加載,根據注冊表設置加載相應的客戶驅動程序,即存儲設備,串口設備或者RNDIS設備。客戶驅動程序即啟動USBFN,引發主機配置設備,配置完成以后即可開始工作。
而USBFN總路線控制器驅動的MDD部分WINCE本身已經提供,PDD只需初始化硬件設備,提供傳輸即可。MDD在初始化時調用UfnPdd_Init函數得到PDD層的函數表,之后會根據需要調用各個函數。PDD還需要提供IST,用以處理各個中斷。需要注意的是USBFN有一個與其它設備不同之處,它的注冊表需要這樣一個設置:
"BusIoctl"=dword:2a0048,用以讓系統加載完設備之后調用值為0x2a0048的IOCTL代碼去完成初始化,其定義為IOCTL_BUS_POSTINIT。
標簽: BaseKnowledge
SOURCES文件詳解
SOURCES文件是WINCE底層開發中最重要的文件之一,主要的配置項如下:
TARGETNAME,定義模塊名稱. TARGETTYPE,模塊的種類,可以是DYNLINK, LIBRARY,EXE. 如果TARGETTYPE是DLL,則可以定義DLLENTRY,將Dll入口定義成別的不是DLLMain的函數,如果DLL的入口是DllMain,則不需要別的定義。 如果TARGETTYPE是EXE,則可以定義EXEENTRY,用于指定EXE的入口函數.
如果TARGETTYPE是LIBRARY,則不需要定義入口函數。
INCLUDES,如果一個模塊需要使用非標準路徑下的頭文件時,需要定義INCLUDES,用于包含更多的頭文件路徑,用法如下:
INCLUDES=$(INCLUDES);\new directory\...,注意定義新的INCLUDES時,需要包含INCLUDES原來的值,否則就需要包含所有可能的目錄。
TARGETLIBS,SOURCELIBS用于定義該模塊需要鏈接哪些庫文件.
TARGETLIBS,如果一個庫以DLL的形式提供給調用者,就需要用TARGETLIBS,它只鏈接一個函數地址,系統執行時會將被鏈接的庫加載。比如coredll.lib就是這樣的庫文件。即動態鏈接。
SOURCELIBS,將庫中的函數實體鏈接進來。即靜態鏈接,用到的函數會在我們的文件中形成一份拷貝。
注意,內核這個執行文件是沒有TARGETLIBS的,GIISR.DLL也不能有TARGETLIBS。
WINCECOD,如果將其定義為1,則編譯器會為每一個文件生成.cod文件,它是一個匯編文件,調試時查看匯編代碼也是一種很好的辦法。
SOURCES,定義該模塊需要哪些源文件.
標簽: BaseKnowledge
多個設備共享同一個硬件中斷
硬件中斷線總是有限的,我們可能需要在已有的系統上做一些擴展,比如將串口擴展成好幾個,有些硬件本身就設計成多個設備共享一條中斷線,比如我的系統中兩個串口就共享同一個CPU中斷,任何一個串口發生中斷以后都會觸發CPU的同一條中斷線,需要判斷別的寄存器來確定是哪個串口發生了什么中斷。
我們可以在OAL中分析各個中斷源,然后返回不同的SYSINTR值,但這種做法擴展性不好。例如,OAL中設值某個中斷源最多會產生三個SYSINTR,但以后擴展成了四個設備,有一個設備就無法正常工作了。
WINCE引入了可裝載中斷處理例程的概念。即在需要與別的設備共享中斷的驅動程序中加載一個ISR,一般使用WINCE提供的GIISR即成滿足需求。將其安裝到內核。OAL中發生中斷時調用NKCallIntChain來得到SYSINTR,這個函數會引起系統逐個調用在該IRQ上加載的所有可裝載的ISR,當某個ISR認為這個中斷是由它引發的時就返回其SYSINTR,否則就返回SYSINTR_CHAIN,系統就會接著調用其它的ISR,甚至所有的ISR都被調用或者有一個ISR返回了正確的SYSINTR。
驅動程序中的調用辦法如下(CE幫助文檔):
if (InstallIsr) {
// Install ISR handler
g_IsrHandle = LoadIntChainHandler(IsrDll, IsrHandler, (BYTE)Irq);
if (!g_IsrHandle) {
DEBUGMSG(ZONE_ERROR, (L"WAVEDEV: Couldn't install ISR handler\r\n"));
} else {
GIISR_INFO Info;
PVOID PhysAddr;
DWORD inIoSpace = 1; // io space
PHYSICAL_ADDRESS PortAddress = {ulIoBase, 0};
if (!TransBusAddrToStatic(PCIBus, 0, PortAddress, ulIoLen, &inIoSpace, &PhysAddr)) {
DEBUGMSG(ZONE_ERROR, (L"WAVEDEV: Failed TransBusAddrToStatic\r\n"));
return FALSE;
}
DEBUGMSG(ZONE_PDD,
(L"WAVEDEV: Installed ISR handler, Dll = '%s', Handler = '%s', Irq =
%d, PhysAddr = 0x%x\r\n", IsrDll, IsrHandler, Irq, PhysAddr));
// Set up ISR handler
Info.SysIntr = ulSysIntr;
Info.CheckPort = TRUE;
Info.PortIsIO = TRUE;
Info.UseMaskReg = FALSE;
Info.PortAddr = (DWORD)PhysAddr + ES1371_dSTATUS_OFF;
Info.PortSize = sizeof(DWORD);
Info.Mask = ES1371_INTSTAT_PENDING;
if (!KernelLibIoControl(g_IsrHandle, IOCTL_GIISR_INFO, &Info, sizeof(Info), NULL, 0, NULL)) {
DEBUGMSG(ZONE_ERROR, (L"WAVEDEV: KernelLibIoControl call failed.\r\n"));
}
}
}
這里需要注意一下,因為ISR在內核態運行,Info.PortAddr必須是系統最原始的虛擬地址,即沒有用VirtualCopy映射過的,從OEMAddressTable中計算出來的虛擬地址。在這個例子中用TransBusAddrToStatic函數可以直接把物理地址轉換成這種地址。而MmMapIoSpace得到是在當前程序空間中的地址,不能使用。而且GIIR要被加載到內核空間,所以在加入到OS包中時需要加上K標志,否則LoadIntChainHandler函數會失敗。
2008年12月19日
一. 介紹
本文主要介紹在不進入KITL模式, 使用CeLog工具來調試Windows mobile設備的方法,該方法可以抓到使用DEBUGMSG打出的log信息,注意:本方法只在RETAIL版本上實驗通過。
二. 工具
Platform Builder
Readlog.exe
CeLogStopFlush.exe
Readlog.exe可以在路徑:_WINCEROOT\SDK\BIN\I386下面找到,CeLogStopFlush.exe
的源代碼可以在_WINCEROOT\Public\Common\SDK\Samples\CeLog\Flush\Stopflush找到,使用build –c可以生成CeLogStopFlush.exe。
三. 步驟
1.建立一個Start Log的快捷方式,具體新建一個文件,復制下面的語句:
83#\Windows\celogflush.exe -buf 0x100000 -time 60000 -n \celog.clg -z 0x00800000 -ui 1
將其重命名為Start Log.lnk
2.檢查手機的\Windows目錄是否有以下兩個文件CeLog.dll and CeLogFlush.exe,如果沒有的話,將它們拷貝到\Windows目錄(release目錄可以找到)
3.拷貝Start Log.lnk和CeLogStopFlush.exe到開始菜單中,修改注冊表,增加或修改下面的鍵值。(注意ZoneCE是16進制的)
[HKEY_LOCAL_MACHINE\System\CeLog]
"Transport"="LocalFile"
"FileName"="celog.clg"
"ZoneCE"=dword:800000
4.點擊Start Log開始抓log
5.點擊CeLogStopFlush.exe停止抓log
6.將根目錄下生成的celog.clg文件拷貝到PC機與Readlog同一級目錄上
7.打開DOS命令行,轉到Readlog.exe目錄中,輸入下面的命令:
Readlog.exe celog.clg celog.log
8.使用文本工具查看celog.log中的log信息,下面是一個log的實例。
2008年12月16日
Linux應用程序安裝與管理
目標:
了解linux應用程序的組成部分
掌握使用RPM工具管理軟件包的方法
掌握應用程序源代碼包的編譯安裝方法
掌握圖形界面下應用程序管理工具的使用
1、Linux應用程序基礎
2、RPM包管理
包管理系統初步:
RPM:RPM Package Manager
http://www.rpm.org
RPM包管理系統:
$ rpm
RPPM包的文件名稱:
bash-3.0-19.2.i386.rpm
bash:軟件名稱。
3.0-19.2:軟件的版本號。
i386:軟件所運行的最低硬件平臺。
rpm:文件的擴展名,用來標識當前文件是rpm格式的軟件包。
RPM包管理功能:
rpm命令配合不同的參數可以實現以下的rpm包的管理功能:
查詢已安裝在linux系統中的RPM軟件包的信息。
查詢RPM軟件包安裝文件的信息。
安裝RPM軟件包到當前linux系統。
從當前linux系統中卸載已安裝的RPM軟件包。
從當前linux系統中升級已安裝的RPM軟件包。
使用rpm命令查詢軟件包:
1、查詢系統中安裝的所有RPM包
$ rpm -qa 查詢當前linux系統中已經安裝的軟件包。
例:$ rpm -qa | grep -i x11 | head -3 察看系統中包含x11字符串的前3行軟件包。
2、查詢軟件包是否安裝
$ rpm –q rpm包名稱 察看系統中指定軟件包是否安。
例: $ rpm -q bash 察看系統中bash軟件包是否安裝。
"rpm -q"命令中指定的軟件包名稱需要準確的拼寫,該命令不會在軟件包的名稱中進行局部匹配的查詢。
3、查詢已安裝軟件包詳細信息
$ rpm –qi RPM包名稱 查詢linux系統中指定名稱軟件包的詳細信息。
例:$ rpm -qi bash 察看bash軟件包的詳細信息。
"rpm -qi"命令的執行結果中包含較詳細的信息,包括:軟件名稱,版本信息,包大小,描述,等。
4、查詢已安裝軟件包中的文件列表
$ rpm –ql RPM包名稱 查詢已安裝軟件包在當前系統中安裝了哪些文件。
例:$ rpm -ql bash | head -3 查看bash軟件在系統中已安裝文件的前3行文件列表。
$ rpm -ql bash | grep bin 用過濾方式察看bash中包含bin字符串的文件列表。
5、查詢系統中文件所屬的軟件包
$ rpm –qf 文件名稱 查詢linux系統中指定文件所屬的軟件包。
例:$ rpm -qf /bin/bash 察看bash文件所屬的軟件包。
bash-3.0-19.2 顯示結果。
6、查詢RPM安裝包文件中的信息
$ rpm –qpi RPM包文件名 察看RPM包未安裝前的詳細信息。
$ rpm –qpl RPM包文件名 察看RPM包未安裝前的文件列表。
"rpm -qpi和rpm -qpl 這兩條命令可作為在安裝軟件包之前對其的了解。
7、rpm命令查詢實例
$ which mount 獲得mount命令的可執行文件路徑。
$ rpm –qf /bin/mount 查詢/bin/mount所屬的軟件包。
$ rpm –qi util-linux 查詢/bin/mount所屬軟件包的詳細信息。
$ rpm –qf util-linux | grep mount 查詢/bin/mount所屬軟件包中包括mount相關所有文件。
使用rpm命令安裝軟件包
1、rpm軟件包地基本安裝
$ rpm –i rpm安裝包文件名 安裝該軟件包中的文件到當前系統,安裝過程不提示任何信息。
2、在安裝軟件包的同時顯示詳細信息
$ rpm –ivh rpm安裝包文件 安裝該軟件包中的文件到當前系統,安裝過程會以百分比的形式
顯示安裝的進度和一些其他信息。
3、RPM軟件包安裝的依賴關系
強制安裝:$ rpm --force –i rpm包文件名
注:要先滿足軟件包的依賴關系后再進行軟件包的安裝,使用強制安裝命令安裝不能保證軟件安裝到系統后一定能
正常運行,因此建議慎重使用。
使用rpm命令卸載軟件包:
1、RPM軟件包的卸載
$ rpm -e 軟件包名稱 軟件包的卸載,在卸載時不顯示任何信息。
注:RPM軟件包的卸載同樣存在依賴關系,只有在沒有依賴關系存在時才能對其進行卸載。
2、rpm軟件包卸載的依賴關系
在使用RPM命令進行卸載時,RPM命令會分析要卸載的軟件包的依賴關系,當存在依賴關系時會自動停止,并顯由
哪個軟件造成的卸載失敗。根據RPM提示的錯誤信息,確定先卸載的軟件包,再卸載被依賴的軟件包。
使用rpm命令升級軟件包:
$ rpm - U rpm安裝包文件名
注:"rpm -u"命令中使用的升級軟件包文件最好使用RED HAT公司針對當前的linux版本官方推出的升級文件,建議不要
使用第三方提供的升級包。
應用程序編譯
開放源代碼應用程序的編譯安裝
(下面以多線程下載軟件"prozilla"的源代碼編譯安裝為例來說明源代碼編譯安裝的整個過程)
編譯應用程序前的準備工作:
1、確認系統中已經安裝了編譯環境
$ rpm -qa | grep gcc 確定當前系統中安裝了gcc編譯器環境。
2、下載prozilla程序的源代碼安裝包文件
略
3、釋放已下載的源代碼軟件包文件
$ tar jxf prozilla-2.0.4.tar.bz2 釋放以下載的源代碼軟件包文件到當前目錄。解壓后的文件
名:prozilla-2.0.4
擴展:tar的xzvf參數用于釋放以tar.gz格式的壓縮包。
4、進入源代碼目錄
$ cd prozilla-2.0.4 進入目錄。
$ pwd 顯示當前目錄路徑。
/home/teacher/download/prozilla-2.0.4 顯示結果。
編譯軟件安裝的路徑:
$ ./configure --prefix=/home/teacher/proz
在prozilla程序的配置中,使用"--prdfix"選項可以指定應用程序編譯后的安裝路徑,如果不使用"--prefix"
選項指定安裝路徑,configure程序將配置prozilla的默認安裝路徑為"/usr/local/bin"目錄。
5、程序編譯過程
$ make 使用make命令進行程序的二進制編譯。
6、程序安裝過程
$ make install
"make install"命令將按照configuer命令的"--prefix"選項中設定的安裝路徑將已編譯完成的應用程序安裝
到目標目錄。
7、驗證編譯安裝的程序
$ ls /home/teacher/proz 察看proz文件夾中的文件。
bin include lib man share
編譯前的配置
$ ./configure - - help
編譯與安裝:
1、程序編譯過程
$ make
2、程序安裝過程
$ make install
3、驗證編譯安裝的程序
使用圖形界面系統工具完成RPM保的安裝
略
2008年12月3日
上述講了堆理論,可能讀者腦袋都已經大了,為此,我們舉個簡單的例子來詳細說明一下驅動程序的開發過程。
例如我們有個USB Mouse設備,設備信息描述如下: Device Descriptor: bcdUSB: 0x0100 bDeviceClass: 0x00 bDeviceSubClass: 0x00 bDeviceProtocol: 0x00 bMaxPacketSize0: 0x08 (8) idVendor: 0x05E3 (Genesys Logic Inc.) idProduct: 0x0001 bcdDevice: 0x0101 iManufacturer: 0x00 iProduct: 0x01 iSerialNumber: 0x00 bNumConfigurations: 0x01
ConnectionStatus: DeviceConnected Current Config value: 0x01 Device Bus Speed: Low Device Address: 0x02 Open Pipes: 1
Endpoint Descriptor: bEndpointAddress: 0x81 Transfer Type: Interrupt wMaxPacketSize: 0x0003 (3) bInterval: 0x0A
可以看出上述設備有一個中斷PIPE,包的最大值為3。可能有人問上述的值怎么得到的,win2k 的DDK中有個usbview的例程,編譯一下,將你的USB設備插到PC機的USB口中,運行usbview.exe即可看得相應的設備信息。
有了這些基本信息,就可以編寫USB設備了,首先聲明一下,下面的代碼取自微軟的USB鼠標樣本程序,版權歸微軟所有,此處僅僅借用來描述一下USB鼠標驅動的開發過程,讀者如需要引用此代碼,需要得到微軟的同意。
首先,必須輸出USBD要求調用的三個函數,首先到設備插入到USB端口時,USBD會調用USBDeviceAttach()函數,相應的代碼如下: extern "C" BOOL USBDeviceAttach( USB_HANDLE hDevice, // USB設備句柄 LPCUSB_FUNCS lpUsbFuncs, // USBDI的函數集合 LPCUSB_INTERFACE lpInterface, // 設備接口描述信息 LPCWSTR szUniqueDriverId, // 設備ID描述字符串。 LPBOOL fAcceptControl, // 返回TRUE,標識我們可以控制此設備, 反之表示不能控制 DWORD dwUnused) { *fAcceptControl = FALSE; // 我們的鼠標設備有特定的描述信息,要檢測是否是我們的設備。 if (lpInterface == NULL) return FALSE; // 打印相關的USB設備接口描述信息。 DEBUGMSG(ZONE_INIT,(TEXT("USBMouse:
DeviceAttach, IF %u, #EP:%u, Class:%u, Sub:%u,Prot:%u\r\n"),
lpInterface->Descriptor.bInterfaceNumber,lpInterface->Descriptor.bNumEndpoints,
lpInterface->Descriptor.bInterfaceClass,lpInterface->Descriptor.bInterfaceSubClass,lpInterface->Descriptor.bInterfaceProtocol));
// 初試數據USB鼠標類,產生一個接受USB鼠標數據的線程 CMouse * pMouse = new CMouse(hDevice, lpUsbFuncs, lpInterface); if (pMouse == NULL) return FALSE;
if (!pMouse->Initialize()) { delete pMouse; return FALSE; }
// 注冊一個監控USB設備事件的回調函數,用于監控USB設備是否已經拔掉。 (*lpUsbFuncs->lpRegisterNotificationRoutine)(hDevice, USBDeviceNotifications, pMouse);
*fAcceptControl = TRUE; return TRUE; }
第二個函數是 USBInstallDriver()函數, 一些基本定義如下: const WCHAR gcszRegisterClientDriverId[] = L"RegisterClientDriverID"; const WCHAR gcszRegisterClientSettings[] = L"RegisterClientSettings"; const WCHAR gcszUnRegisterClientDriverId[] = L"UnRegisterClientDriverID"; const WCHAR gcszUnRegisterClientSettings[] = L"UnRegisterClientSettings"; const WCHAR gcszMouseDriverId[] = L"Generic_Sample_Mouse_Driver";
函數接口如下: extern "C" BOOL USBInstallDriver( LPCWSTR szDriverLibFile) // @parm [IN] - Contains client driver DLL name { BOOL fRet = FALSE; HINSTANCE hInst = LoadLibrary(L"USBD.DLL");
// 注冊USB設備信息 if(hInst) { LPREGISTER_CLIENT_DRIVER_ID pRegisterId = (LPREGISTER_CLIENT_DRIVER_ID) GetProcAddress(hInst, gcszRegisterClientDriverId);
LPREGISTER_CLIENT_SETTINGS pRegisterSettings = (LPREGISTER_CLIENT_SETTINGS) GetProcAddress(hInst, gcszRegisterClientSettings);
if(pRegisterId && pRegisterSettings) { USB_DRIVER_SETTINGS DriverSettings;
DriverSettings.dwCount = sizeof(DriverSettings);
// 設置我們的特定的信息。 DriverSettings.dwVendorId = USB_NO_INFO; DriverSettings.dwProductId = USB_NO_INFO; DriverSettings.dwReleaseNumber = USB_NO_INFO;
DriverSettings.dwDeviceClass = USB_NO_INFO; DriverSettings.dwDeviceSubClass = USB_NO_INFO; DriverSettings.dwDeviceProtocol = USB_NO_INFO;
DriverSettings.dwInterfaceClass = 0x03; // HID DriverSettings.dwInterfaceSubClass = 0x01; // boot device DriverSettings.dwInterfaceProtocol = 0x02; // mouse
fRet = (*pRegisterId)(gcszMouseDriverId);
if(fRet) { fRet = (*pRegisterSettings)(szDriverLibFile, gcszMouseDriverId, NULL, &DriverSettings);
if(!fRet) { //BUGBUG unregister the Client Driver’s ID } } } else { RETAILMSG(1,(TEXT("!USBMouse: Error getting USBD function pointers\r\n"))); } FreeLibrary(hInst); } return fRet; } 上述代碼主要用于產生USB設備驅動程序需要的注冊表信息,需要注意的是:USB設備驅動程序不使用標準的注冊表函數,而是使用RegisterClientDriverID()和RegisterClientSettings來注冊相應的設備信息。
另外一個函數是USBUninstallDriver()函數,具體代碼如下: extern "C" BOOL USBUnInstallDriver() { BOOL fRet = FALSE; HINSTANCE hInst = LoadLibrary(L"USBD.DLL");
if(hInst) { LPUN_REGISTER_CLIENT_DRIVER_ID pUnRegisterId = (LPUN_REGISTER_CLIENT_DRIVER_ID) GetProcAddress(hInst, gcszUnRegisterClientDriverId);
LPUN_REGISTER_CLIENT_SETTINGS pUnRegisterSettings = (LPUN_REGISTER_CLIENT_SETTINGS) GetProcAddress(hInst, gcszUnRegisterClientSettings);
if(pUnRegisterSettings) { USB_DRIVER_SETTINGS DriverSettings;
DriverSettings.dwCount = sizeof(DriverSettings); // 必須填入與注冊時相同的信息。 DriverSettings.dwVendorId = USB_NO_INFO; DriverSettings.dwProductId = USB_NO_INFO; DriverSettings.dwReleaseNumber = USB_NO_INFO;
DriverSettings.dwDeviceClass = USB_NO_INFO; DriverSettings.dwDeviceSubClass = USB_NO_INFO; DriverSettings.dwDeviceProtocol = USB_NO_INFO;
DriverSettings.dwInterfaceClass = 0x03; // HID DriverSettings.dwInterfaceSubClass = 0x01; // boot device DriverSettings.dwInterfaceProtocol = 0x02; // mouse
fRet = (*pUnRegisterSettings)(gcszMouseDriverId, NULL, &DriverSettings); }
if(pUnRegisterId) { BOOL fRetTemp = (*pUnRegisterId)(gcszMouseDriverId); fRet = fRet ? fRetTemp : fRet; } FreeLibrary(hInst); } return fRet; } 此函數主要用于刪除USBInstallDriver()時創建的注冊表信息,同樣的它使用自己的函數接口UnRegisterClientDriverID()和UnRegisterClientSettings()來做相應的處理。
另外一個需要處理的注冊的監控通知函數USBDeviceNotifications(): extern "C" BOOL USBDeviceNotifications(LPVOID lpvNotifyParameter, DWORD dwCode, LPDWORD * dwInfo1, LPDWORD * dwInfo2, LPDWORD * dwInfo3, LPDWORD * dwInfo4) { CMouse * pMouse = (CMouse *)lpvNotifyParameter;
switch(dwCode) { case USB_CLOSE_DEVICE: //刪除相關的資源。 delete pMouse; return TRUE; } return FALSE; }
USB鼠標的類的定義如下: class CMouse { public: CMouse::CMouse(USB_HANDLE hDevice, LPCUSB_FUNCS lpUsbFuncs, LPCUSB_INTERFACE lpInterface); ~CMouse();
BOOL Initialize(); private: // 傳輸完畢調用的回調函數 static DWORD CALLBACK MouseTransferCompleteStub(LPVOID lpvNotifyParameter); // 中斷處理函數 static ULONG CALLBACK CMouse::MouseThreadStub(PVOID context); DWORD MouseTransferComplete(); DWORD MouseThread();
BOOL SubmitInterrupt(); BOOL HandleInterrupt();
BOOL m_fClosing; BOOL m_fReadyForMouseEvents;
HANDLE m_hEvent; HANDLE m_hThread;
USB_HANDLE m_hDevice; USB_PIPE m_hInterruptPipe; USB_TRANSFER m_hInterruptTransfer;
LPCUSB_FUNCS m_lpUsbFuncs; LPCUSB_INTERFACE m_pInterface;
BOOL m_fPrevButton1; BOOL m_fPrevButton2; BOOL m_fPrevButton3;
// 數據接受緩沖區。 BYTE m_pbDataBuffer[8]; };
具體實現如下:
// 構造函數,初始化時調用 CMouse::CMouse(USB_HANDLE hDevice, LPCUSB_FUNCS lpUsbFuncs, LPCUSB_INTERFACE lpInterface) { m_fClosing = FALSE; m_fReadyForMouseEvents = FALSE; m_hEvent = NULL; m_hThread = NULL;
m_hDevice = hDevice; m_hInterruptPipe = NULL; m_hInterruptTransfer = NULL;
m_lpUsbFuncs = lpUsbFuncs; m_pInterface = lpInterface;
m_fPrevButton1 = FALSE; m_fPrevButton2 = FALSE; m_fPrevButton3 = FALSE;
memset(m_pbDataBuffer, 0, sizeof(m_pbDataBuffer)); }
// 析構函數,用于清除申請的資源。 CMouse::~CMouse() { // 通知系統去關閉相關的函數接口。 m_fClosing = TRUE;
// Wake up the connection thread again and give it time to die. if (m_hEvent != NULL) { // 通知關閉數據接受線程。 SetEvent(m_hEvent);
if (m_hThread != NULL) { DWORD dwWaitReturn;
dwWaitReturn = WaitForSingleObject(m_hThread, 1000); if (dwWaitReturn != WAIT_OBJECT_0) { TerminateThread(m_hThread, DWORD(-1)); } CloseHandle(m_hThread); m_hThread = NULL; } CloseHandle(m_hEvent); m_hEvent = NULL; }
if(m_hInterruptTransfer) (*m_lpUsbFuncs->lpCloseTransfer)(m_hInterruptTransfer);
if(m_hInterruptPipe) (*m_lpUsbFuncs->lpClosePipe)(m_hInterruptPipe); }
// 初始化USB鼠標驅動程序 BOOL CMouse::Initialize() { LPCUSB_DEVICE lpDeviceInfo = (*m_lpUsbFuncs->lpGetDeviceInfo)(m_hDevice);
// 檢測配置:USB鼠標應該只有一個中斷管道 if ((m_pInterface->lpEndpoints[0].Descriptor.bmAttributes & USB_ENDPOINT_TYPE_MASK) != USB_ENDPOINT_TYPE_INTERRUPT) { RETAILMSG(1,(TEXT("!USBMouse: EP 0 wrong type (%u)!\r\n"), m_pInterface->lpEndpoints[0].Descriptor.bmAttributes)); return FALSE; } DEBUGMSG(ZONE_INIT,(TEXT("USBMouse: EP 0:MaxPacket: %u, Interval: %u\r\n"), m_pInterface->lpEndpoints[0].Descriptor.wMaxPacketSize, m_pInterface->lpEndpoints[0].Descriptor.bInterval));
m_hInterruptPipe = (*m_lpUsbFuncs->lpOpenPipe)(m_hDevice, &m_pInterface->lpEndpoints[0].Descriptor);
if (m_hInterruptPipe == NULL) { RETAILMSG(1,(TEXT("Mouse: Error opening interrupt pipe\r\n"))); return (FALSE); } m_hEvent = CreateEvent(NULL, FALSE, FALSE, NULL); if (m_hEvent == NULL) { RETAILMSG(1,(TEXT("USBMouse: Error on CreateEvent for connect event\r\n"))); return(FALSE); } // 創建數據接受線程 m_hThread = CreateThread(0, 0, MouseThreadStub, this, 0, NULL); if (m_hThread == NULL) { RETAILMSG(1,(TEXT("USBMouse: Error on CreateThread\r\n"))); return(FALSE); }
return(TRUE); }
// 從USB鼠標設備中讀出數據,產生相應的鼠標事件。 BOOL CMouse::SubmitInterrupt() { if(m_hInterruptTransfer) (*m_lpUsbFuncs->lpCloseTransfer)(m_hInterruptTransfer);
// 從USB鼠標PIPE中讀數據 m_hInterruptTransfer = (*m_lpUsbFuncs->lpIssueInterruptTransfer) (m_hInterruptPipe, MouseTransferCompleteStub, this, USB_IN_TRANSFER | USB_SHORT_TRANSFER_OK, // 表示讀數據 min(m_pInterface->lpEndpoints[0].Descriptor.wMaxPacketSize, sizeof(m_pbDataBuffer)), m_pbDataBuffer, NULL);
if (m_hInterruptTransfer == NULL) { DEBUGMSG(ZONE_ERROR,(L "!USBMouse: Error in IssueInterruptTransfer\r\n")); return FALSE; } else { DEBUGMSG(ZONE_TRANSFER,(L"USBMouse::SubmitInterrupt,Transfer:0x%X\r\n", m_hInterruptTransfer)); } return TRUE; }
// 處理鼠標中斷傳輸的數據 BOOL CMouse::HandleInterrupt() { DWORD dwError; DWORD dwBytes;
DWORD dwFlags = 0; INT dx = (signed char)m_pbDataBuffer[1]; INT dy = (signed char)m_pbDataBuffer[2];
BOOL fButton1 = m_pbDataBuffer[0] & 0x01 ? TRUE : FALSE; BOOL fButton2 = m_pbDataBuffer[0] & 0x02 ? TRUE : FALSE; BOOL fButton3 = m_pbDataBuffer[0] & 0x04 ? TRUE : FALSE;
if (!(*m_lpUsbFuncs->lpGetTransferStatus)(m_hInterruptTransfer, &dwBytes,&dwError)) { DEBUGMSG(ZONE_ERROR,(TEXT("!USBMouse: Error in GetTransferStatus(0x%X)\r\n"), m_hInterruptTransfer)); return FALSE; } else { DEBUGMSG(ZONE_TRANSFER,(TEXT("USBMouse::HandleInterrupt, hTransfer 0x%X complete (%u bytes, Error:%X)\r\n"), m_hInterruptTransfer,dwBytes,dwError)); }
if (!SubmitInterrupt()) return FALSE;
if(dwError != USB_NO_ERROR) { DEBUGMSG(ZONE_ERROR,(TEXT("!USBMouse: Error 0x%X in interrupt transfer\r\n"),dwError)); return TRUE; }
if(dwBytes < 3) { DEBUGMSG(ZONE_ERROR,(TEXT("!USBMouse: Invalid byte cnt %u from interrupt transfer\r\n"),dwBytes)); return TRUE; }
if(dx || dy) dwFlags |= MOUSEEVENTF_MOVE;
if(fButton1 != m_fPrevButton1) { if(fButton1) dwFlags |= MOUSEEVENTF_LEFTDOWN; else dwFlags |= MOUSEEVENTF_LEFTUP; }
if(fButton2 != m_fPrevButton2) { if(fButton2) dwFlags |= MOUSEEVENTF_RIGHTDOWN; else dwFlags |= MOUSEEVENTF_RIGHTUP; }
if(fButton3 != m_fPrevButton3) { if(fButton3) dwFlags |= MOUSEEVENTF_MIDDLEDOWN; else dwFlags |= MOUSEEVENTF_MIDDLEUP; }
m_fPrevButton1 = fButton1; m_fPrevButton2 = fButton2; m_fPrevButton3 = fButton3;
DEBUGMSG(ZONE_EVENTS, (TEXT("USBMouse event: dx:%d, dy:%d, dwFlags:0x%X (B1:%u, B2:%u, B3:%u)\r\n"), dx,dy,dwFlags,fButton1,fButton2,fButton3));
// 通知系統產生鼠標事件 if (m_fReadyForMouseEvents) mouse_event(dwFlags, dx, dy, 0, 0); else m_fReadyForMouseEvents = IsAPIReady(SH_WMGR);
return TRUE; }
DWORD CALLBACK CMouse::MouseTransferCompleteStub(LPVOID lpvNotifyParameter) { CMouse * pMouse = (CMouse *)lpvNotifyParameter; return(pMouse->MouseTransferComplete()); }
// 數據傳輸完畢回調函數 DWORD CMouse::MouseTransferComplete() { if (m_hEvent) SetEvent(m_hEvent); return 0; }
ULONG CALLBACK CMouse::MouseThreadStub(PVOID context) { CMouse * pMouse = (CMouse *)context; return(pMouse->MouseThread()); }
// USB鼠標線程 DWORD CMouse::MouseThread() { DEBUGMSG(ZONE_INIT,(TEXT("USBMouse: Worker thread started\r\n"))); SetThreadPriority(GetCurrentThread(), THREAD_PRIORITY_HIGHEST);
if (SubmitInterrupt()) { while (!m_fClosing) { WaitForSingleObject(m_hEvent, INFINITE);
if (m_fClosing) break;
if ((*m_lpUsbFuncs->lpIsTransferComplete)(m_hInterruptTransfer)) { if (!HandleInterrupt()) break; } else { RETAILMSG(1,(TEXT("!USBMouse: Event signalled, but transfer not complete\r\n"))); // The only time this should happen is if we get an error on the transfer ASSERT(m_fClosing || (m_hInterruptTransfer == NULL)); break; } } } RETAILMSG(1,(TEXT("USBMouse: Worker thread exiting\r\n"))); return(0); }
看
到了沒有,其實USB的驅動程序編寫就這么簡單,類似的其他設備,例如打印機設備,就有Bulk OUT
PIPE,需要Bulk傳輸,那就需要了解一下IssueBulkTransfer()的應用。當然如果是開發USB Mass Storage
Disk的驅動,那就需要了解更多的協議,例如Bulk-Only Transport協議等。
微軟的Windows CE.NET的Platform Build中已經帶有USB Printer和USB Mass Storage Disk的驅動的源代碼了,好好研究一下,你一定回受益非淺的。
參考資料: 1. 微軟出版社 <<Windows Ce Device Driver Kit>> 2. <<Universal Serial Bus Specification 1.1>> 來自http:://www.usb.org
2008年12月2日
這里我們主要討論的是CE的中斷建立和中斷相應的大概流程以及所涉及的代碼位置。這里所講述的,是針對ARM平臺的。在CE的中斷處理里面,有一部分工作是CE Kernel完成的,有一部分工作是要由OEM完成的。
Kernel代碼工作
ExVector.s:中斷向量定義,里面定義的是armtrap.s的函數地址
Armtrap.s:中斷處理定義,最重要是里面的IRQHandler函數,而其中最重要的是CALL OEMInterruptHandler
Mdarm.c:中斷向量加載
Kdriver.c:NKCallIntChain函數:把IRQ轉換為SysIntr,值得留意的是pIntChainTable[],是IRQ所對應的ISR處理程序的入口,其中最主要的是其成員函數pfnHandler。pfnHandler的填充,是在HookIntChain里面,這個函數是ISR在初始化的時候調用的。在這個函數里面,如果pIntChainTable為空,則返回SYSINTR_CHAIN,如果pIntChainTable[]不為空,則調用pfnHandler得到一個sysintr值,然后返回之。
OEM定義工作:Oalintr.c:OEMInterruptHandler函數,通過查詢硬件的中斷寄存器,得到硬件IRQ號。對于EINT04-23的中斷,通過EINTMASK寄存器,得到相對應的系統IRQ。注意,這里的IRQ是CE定義的IRQ,是系統硬件IRQ的擴展。然后調用NKCallIntChain看看這個IRQ是否是一個Chain的Interrupt。如果函數返回SYSINTR_CHAIN或者返回一個不合法的sysintr,則通過OALIntrTranslateIrq把IRQ轉化為sysintr。如果是一個合法的sysintr,則返回該值。
單一ISR的Device,主要通過OEMInterruptHandler處理,在OEMInterruptHandler沒有定義的IRQ,可以在OAL里面或者驅動的加載里面,通過HookInterrupt函數進行關聯。
多個ISR的Device,通常這是總線設備的需求,因為總線設備上面通常串有幾個設備。這些總線上的設備,需要有一個ISR判斷究竟是哪個設備發出的中斷。這個ISR,是一個DLL的程序,設備驅動必須在初始化的時候通過LoadIntChainHandler(文件名,函數名,irq)加載這個DLL程序。LoadIntChainHandler的定義在kdriver.c的NKLoadIntChainHandler里面。對于大多數的總線設備,可以利用微軟已經寫好的giisr.dll來實現。giisr的實現代碼在Public\common\oak\drivers下面。
對于總線設備,如果利用GIISR的話,原理如下:
總線設備驅動在初始化的時候,通過LoadIntChainHandler加載GIISR,而加載的時候,LoadIntChainHandler會調用GIISR的CreateInstance創建一個實例,GIISR會返回一個index值給LoadIntChainHandler,以標示實例,LoadIntChainHandler則會返回一個Handle給驅動,驅動則根據這個Handle存取GIISR。得到這個handle之后,初始化還需要包括從reg表里面讀出相關的初始化參數,對GIISR進行賦值,譬如Port Address,Mask Address,SysIntr等。
驅動程序在初始化的時候:
1、創建一個Event(CreateEvent)
2、然后用InterruptInitialize函數把sysintr和這個Event相關聯
3、Kick-off一個Thread(IST)
4、這個Thread最終是WaitForSingleObject(EventID)
具體的例子,可以參閱USBFN的例子:sc2410pdd.cpp里面,UfnPdd_Start函數;
Published Sunday, June 10, 2007 7:13 PM
by
ningling
2008年11月28日
這是我從1月6日開始主持天極網論壇嵌入式開發版以來第一次發表文章,加上以前瑣碎的文章共
計30篇。研究的越多就越感覺自己懂的太少,其實在驅動開發方面我還是個菜鳥,我是想再次拋磚引玉,讓做驅動有N年經驗的人奉獻一點出來,讓大家減少一些
研究驅動源碼而又缺少注釋所帶來的痛苦。
我想即使讀者看過微軟的關于驅動開發的培訓教材和CE幫助文檔中的驅動部分,頭腦中仍然一片茫然。要想真正了解驅動程序必須結合一些驅動程序源碼,在此我以串口驅動程序(COM16550)中初始化過程為線索簡單講一講驅動開發的基礎知識。
Windows
CE下的串口驅動程序能夠處理所有I/O行為類似串口的設備,包括基于16450、16550
UART(通用異步收發芯片)的設備和一些采用DMA的設備,常見的有9針串口、紅外I/O口、Modem等。在%_WINCEROOT%\Public
\Common\OAK\Drivers\Serial目錄下,COM_MDD2子目錄包含新的串口驅動MDD層函數代碼。COM16550子目錄包含串
口驅動PDD層代碼。SER16550子目錄包含的一系列函數專用于控制與16550兼容的UART,這樣PDD層的主要工作就是調用SER16550中
的函數。還有一個ISR16550子目錄包含的是串口驅動程序專用的可安裝ISR(中斷服務例程),而很多硬件設備驅動程序采用CE默認的可安裝ISR
giisr.dll。一般串口設備相應的注冊表設置例子及意義如下:
鍵 |
意義 |
"SysIntr"=dword:13 |
串口1的中斷ID為十進制13 |
"IoBase"=dword:02F8 |
串口1的IO空間首地址為十六進制2F8 |
"IoLen"=dword:8 |
串口1的IO空間長度為8個字節 |
"DeviceArrayIndex"=dword:0 |
串口1的索引,是1的由來 |
"Order"=dword:0 |
串口1驅動的加載順序 |
"DeviceType"=dword:0 |
串口1的設備類型 |
"DevConfig"=hex: 10,00 .... |
串口1在與Modem設備通訊時的配置,如波特率、奇偶校檢等 |
"FriendlyName"="COM1:" |
串口1在撥號程序中顯示的名字 |
"Tsp"="Unimodem.dll" |
串口1 被用于與Modem設備通訊的時候要加載的TSP(TAPI Service provider)DLL |
"Prefix"="COM" |
串口1的流接口的前綴 |
"Dll"="com16550.Dll" |
串口1的驅動程序DLL |
SysIntr
由CE在文件Nkintr.h中預定義,用于唯一標識中斷設備。OEM可以在文件Oalintr.h中定義自己的SysIntr。常見的預定義
SysIntr有SYSINTR_NOP(中斷只由ISR處理,IST不再處理),SYSINTR_RESCHED(重新調度線
程),SYSINTR_DEVICES(由CE預定義的設備中斷ID的基值),SYSINTR_PROFILE、SYSINTR_TIMING、
SYSINTR_FIRMWARE等都是基于SYSINTR_DEVICES定義的。IoBase是串口1的IO地址空間的首地址,IoLen是IO空間
的大小。IO地址空間只存在于x86平臺,如果在其它平臺硬件寄存器必須映射到物理地址空間,那子鍵的名稱為MemBase和MemLen。在x86平臺
更多硬件的寄存器由于IO空間的局限也映射到物理地址空間。DeviceArrayIndex是設備的索引,用于區分同類型的設備。Prefix是流驅動
程序的前綴,當應用程序調用CreateFile函數傳遞COM1:參數時,文件系統負責與串口驅動程序通信,串口驅動程序是在CE啟動時由
device.exe加載的。
下面從MDD
層函數COM_Init開始探索串口驅動的初始化過程。COM_Init是在串口設備被檢測后由設備管理器device.exe調用的,主要的作用是初始
化設備,它的唯一參數Identifier是由device.exe傳遞的,其類型是一個字符串指針,字符串的內容是HLM\Drivers
\Active\xx,xx是一個十進制數(device.exe會跟蹤系統中每個驅動程序,把加載的驅動程序記錄在Active鍵下)。
COM_Init先分配一個HW_INDEP_INFO結構體,這個結構體是獨立于串口硬件的頭信息(MDD、PDD、SER16550都包含自己獨特的
結構體,具體的結構體定義請參見串口驅動源碼),分配之后再初始化結構體中每個成員,初始化結構體后調用
OpenDeviceKey((LPCTSTR)Identifier)打開HLM\Drivers\Active\xx\Key包含的注冊表路徑,在這
里路徑一般為HLM\Drivers\BuiltIn\Serial,即串口的驅動程序信息在注冊表中所處的位置。COM_Init接著在HLM
\Drivers\BuiltIn\Serial下查詢DeviceArrayIndex、Priority256的值,Priority256指定了驅
動程序的優先級,如果沒有就用默認的優先級。接下來調用GetSerialObject(DeviceArrayIndex),這個函數由PDD層定義,
返回HWOBJ結構體,這個結構體主要包含PDD層和SER16550定義的函數的指針。也就是說MDD通過調用這個函數才能調用底層實現的函數。接下來
的大多數工作都是調用底層函數實現初始化。第一個調用的底層函數SerInit主要設置由用戶設置的硬件配置,例如線路控制、波特率。它調用
Ser_GetRegistryData函數得到保存在注冊表中的硬件信息,Ser_GetRegistryData在內部調用系統提供的
DDKReg_GetIsrInfoDDK和DDKReg_GetWindowInfo函數得到在HLM\Drivers\BuiltIn\Serial
下保存的IRQ、SysIntr、IsrDll、IsrHandler、IoBase、IoLen。IRQ是邏輯中斷號,IsrDll表示當前驅動程序的
可安裝ISR所在的DLL名稱,IsrHandler
表示可安裝ISR的函數名稱。在這里順便提一下可安裝ISR,讀者在我以前發表的關于OAL的文章中可以了解到OEM在OEMInit函數中關聯IRQ和
SysIntr,當硬件設備發生中斷時,ISR會禁止同級和低級中斷,然后根據IRQ返回關聯的SysIntr,內核根據ISR返回的SysIntr喚醒
相應的IST(SysIntr與IST創建的Event關聯),IST處理中斷之后調用InterruptDone解除中斷禁止。在OEMInit中關聯
的缺點是一旦編譯了CE內核后就無法添加這種關聯了,而一些硬件設備會隨時插拔或者共享中斷,要關聯這樣的硬件設備解決方法就是可安裝ISR,可安裝
ISR專用于處理指定的硬件設備發出的中斷,所以如果硬件設備需要可安裝ISR必須在注冊表中添加IsrDll、IsrHandler。多數硬件設備采用
CE默認的可安裝ISR giisr.dll,格式如下:
"IsrDll"="giisr.dll" "IsrHandler"="ISRHandler"
如果一個硬件驅動程序需要可安裝ISR而開發者又不想自己寫一個,那么可以利用giisr.dll來實現。除了在注冊表中添加如上所示外,還要在驅動程序中調用相關函數注冊可安裝ISR。偽代碼如下:
g_IsrHandle = LoadIntChainHandler(IsrDll, IsrHandler, (BYTE)Irq); GIISR_INFO Info; PHYSICAL_ADDRESS PortAddress = {PhysAddr, 0}; TransBusAddrToStatic(BusType, dwBusNumber, PortAddress, dwAddrLen, &dwIOSpace, &(PVOID)PhysAddr) Info.SysIntr = dwSysIntr; Info.CheckPort = TRUE; Info.PortIsIO = (dwIOSpace) ? TRUE : FALSE; Info.UseMaskReg = TRUE; Info.PortAddr = PhysAddr + 0x0C; Info.PortSize = sizeof(DWORD); Info.MaskAddr = PhysAddr + 0x10; KernelLibIoControl(g_IsrHandle, IOCTL_GIISR_INFO, &Info, sizeof(Info), NULL, 0, NULL);
LoadIntChainHandler
函數負責注冊可安裝ISR,參數1為DLL名稱,參數2為ISR函數名稱,參數3為IRQ。TransBusAddrToStatic函數在后面講。如果
要利用giisr.dll作為可安裝ISR,必須先填充GIISR_INFO結構體,CheckPort=TRUE表示giisr要檢測指定的寄存器來確
定當前發出中斷的是否是這個設備。PortIsIO表示寄存器地址屬于哪個地址空間,FALSE表示是內定空間,TRUE表示IO空間。
UseMaskReg=TRUE表示設備有一個掩碼寄存器,專用于指定當前設備是否是中斷源,也就是發出中斷,而MaskAddr表示掩碼寄存器的地址。
如果對Info.Mask賦值,那么PortAddr表示一個特殊的寄存器地址,這個寄存器的值與Mask的值&運算的結果如果為真,則證明當前
設備是中斷源,否則返回SYSINTR_CHAIN(表示當前ISR沒有處理中斷,內核將調用ISR鏈中下一個ISR),如果
UseMaskReg=TRUE,那么MaskReg寄存器的值與PortAddr指定的寄存器的值&運算的結果如果為真,則證明當前設備是中斷
源。
函數SerInit接著調用函數
Ser_InternalMapRegisterAddresses轉換IO地址并且映射地
址,Ser_InternalMapRegisterAddresses在內部調用系統提供的HalTranslateBusAddress(Isa,
0, ioPhysicalBase, &inIoSpace,
&ioPhysicalBase)函數將與總線相關的地址轉換為系統地址,參數1為總線類型,參數2為總線號,參數3為要轉換的地址
(PHYSICAL_ADDRESS類型,實際是LARGE_INTEGER型),參數4指定寄存器地址屬于IO地址空間還是物理地址空間,參數5返回轉
換后的物理地址。觀察HalTranslateBusAddress的源碼得知如果是在x86平臺,這個函數除了把參數3賦給了參數5其余什么都沒有做,
而非x86平臺將inIoSpace的值置為0,表示一定是物理地址。在調用HalTranslateBusAddress前要確定從注冊表中得到的寄存
器地址到底是屬于哪個地址空間的,例如:
ULONG inIoSpace = 1; ///1表示是IO空間 PHYSICAL_ADDRESS ioPhysicalBase = {iobase, 0}; ///相當于ioPhysicalBase.LowPart = iobase
在地址轉換后就要將轉換后的地址映射到驅動程序(一般IST和應用程序一樣運行在用戶模式)能夠訪問的虛擬地址空間(0x80000000以下)和ISR能夠訪問的靜態虛擬地址空間中(0x80000000以上)。例如:
////如果地址屬于物理地址空間 ioPortBase = (PUCHAR)MmMapIoSpace(ioPhysicalBase, Size, FALSE); TransBusAddrToStatic(Isa, 0, ioPhysicalBase, Size, &inIoSpace, ppStaticAddress);
MmMapIoSpace函數負責將物理地址映射到驅動程序能夠訪問的虛擬地址空間中,通過源碼分析MmMapIoSpace在內部分別調用:
pVirtualAddress =VirtualAlloc(0, SourceSize, MEM_RESERVE, PAGE_NOACCESS); VirtualCopy(pVirtualAddress, (PVOID)(SourcePhys >> 8), SourceSize, PAGE_PHYSICAL | PAGE_READWRITE | (CacheEnable ? 0 : PAGE_NOCACHE));
VirtualAlloc
分配一塊和MemLen一樣大小的虛擬地址空間,因為參數1為0,所以內核自動分配。一般MemLen小于2MB,所以會在應用程序的地址空間中分配。
VirtualCopy負責將硬件設備寄存器的物理地址與VirtualAlloc分配的虛擬地址做一個映射關系,這樣驅動程序訪問
PvirtualAddress實際上就是訪問第一個寄存器。因為硬件設備寄存器的物理地址一定是在512MB(CE支持RAM的最大值)以上,所以除了
最后的參數要加PAGE_PHYSICAL外,第二個參數物理地址也要右移8位(或者除以256)。映射硬件寄存器當然PAGE_NOCACHE是必須加
的。TransBusAddrToStatic函數負責將物理地址映射到ISR能夠訪問的靜態虛擬地址空間中,當出現中斷共享時,ISR要負責訪問硬件設
備的某一個寄存器來判斷中斷源,所以將寄存器的物理地址映射到靜態虛擬地址空間中是必要的(ISR只能訪問靜態的虛擬地址空間)。所謂靜態虛擬地址空間是
指在OEMAddressTable中定義的虛擬地址空間(當然是0x80000000以上)。在x86平臺一般這個表只定義RAM的物理地址與虛擬地址
對應關系,而硬件設備的寄存器地址并不在該表中定義,所以如果要創建一塊靜態的虛擬地址空間供ISR訪問,必須在此之前調用
CreateStaticMapping函數在0xC4000000到0xE0000000虛擬地址空間中分配。
TransBusAddrToStatic函數在內部就是調用了CreateStaticMapping函數。注:硬件設備的寄存器地址也可以在
OEMAddressTable中定義。
////如果地址屬于IO空間 ioPortBase = (PUCHAR)ioPhysicalBase.LowPart; *ppStaticAddress=ioPortBase
這種情況只屬于x86平臺,是IO空間就可以直接訪問,即使是用戶模式。
SerInit
函數接著初始化SER_INFO結構體成員,之后調用SL_Init函數,這個函數在ser16550中定義,負責初始化SER16550_INFO結構
體,在這個結構體中保存串口8個寄存器的地址。SerInit函數執行完畢后COM_Init函數創建接收緩沖區,然后調用
StartDispatchThread函數初始化中斷并且創建IST。StartDispatchThread函數在內部調用
InterruptInitialize函數關聯SysIntr和Event,然后調用InterruptDone函數告訴內核當前串口可以中斷處理,接
著調用CreateThread函數創建IST線程。(over吧,再往下說就和串口硬件有關了,看多了沒注釋的代碼我也煩!!)
here it is .. you need the DDK for that , Gary Nebbett is the author:
#include "ntdll.h"
#include <stdlib.h>
#include <stdio.h>
#include "ntddk.h"
#define DUPLICATE_SAME_ATTRIBUTES 0x00000004
#pragma comment(lib,"ntdll.lib")
BOOL EnablePrivilege(PCSTR name)
{
TOKEN_PRIVILEGES priv = {1, {0, 0, SE_PRIVILEGE_ENABLED}};
LookupPrivilegeValue(0, name, &priv.Privileges[0].Luid);
HANDLE hToken;
OpenProcessToken(GetCurrentProcess(), TOKEN_ADJUST_PRIVILEGES, &hToken);
AdjustTokenPrivileges(hToken, FALSE, &priv, sizeof priv, 0, 0);
BOOL rv = GetLastError() == ERROR_SUCCESS;
CloseHandle(hToken);
return rv;
}
int main(int argc, char *argv[])
{
if (argc == 1) return 0;
ULONG pid = strtoul(argv[1], 0, 0);
EnablePrivilege(SE_DEBUG_NAME);
HANDLE hProcess = OpenProcess(PROCESS_DUP_HANDLE, FALSE, pid);
ULONG n = 0x1000;
PULONG p = new ULONG[n];
while (NT::ZwQuerySystemInformation(NT::SystemHandleInformation, p, n * sizeof *p, 0)
== STATUS_INFO_LENGTH_MISMATCH)
delete [] p, p = new ULONG[n *= 2];
NT::PSYSTEM_HANDLE_INFORMATION h = NT::PSYSTEM_HANDLE_INFORMATION(p + 1);
for (ULONG i = 0; i < *p; i++) {
if (h[i].ProcessId == pid) {
HANDLE hObject;
if (NT::ZwDuplicateObject(hProcess, HANDLE(h[i].Handle), NtCurrentProcess(), &hObject,
0, 0, DUPLICATE_SAME_ATTRIBUTES)
!= STATUS_SUCCESS) continue;
NT::OBJECT_BASIC_INFORMATION obi;
NT::ZwQueryObject(hObject, NT::ObjectBasicInformation, &obi, sizeof obi, &n);
printf("%p %04hx %6lx %2x %3lx %3ld %4ld ",
h[i].Object, h[i].Handle, h[i].GrantedAccess,
int(h[i].Flags), obi.Attributes,
obi.HandleCount - 1, obi.PointerCount - 2);
n = obi.TypeInformationLength + 2;
NT::POBJECT_TYPE_INFORMATION oti = NT::POBJECT_TYPE_INFORMATION(new CHAR[n]);
NT::ZwQueryObject(hObject, NT::ObjectTypeInformation, oti, n, &n);
printf("%-14.*ws ", oti[0].Name.Length / 2, oti[0].Name.Buffer);
n = obi.NameInformationLength == 0
? MAX_PATH * sizeof (WCHAR) : obi.NameInformationLength;
NT::POBJECT_NAME_INFORMATION oni = NT::POBJECT_NAME_INFORMATION(new CHAR[n]);
NTSTATUS rv = NT::ZwQueryObject(hObject, NT::ObjectNameInformation, oni, n, &n);
if (NT_SUCCESS(rv))
printf("%.*ws", oni[0].Name.Length / 2, oni[0].Name.Buffer);
printf("\n");
CloseHandle(hObject);
}
}
delete [] p;
CloseHandle(hProcess);
return 0;
}
|