① 求教,linux下網口虛擬串口驅動程序
開發虛擬串口驅動程序
虛擬串口就是當本地並沒有對應的串口硬體設備,而為應用層提供串口設備一樣的系統調用介面,以兼容原本使用本地串口的應用軟體的「虛」設備。本文作者給出了一種在Windows平台上實現虛擬串口的方法,由此實現的「串口」具有真實串口完全相同的系統調用介面。
在很多應用中需要用到虛擬串口,如在Modem卡出現之前,已經有了接在計算機串口上的外部Modem,而且各種拔號程序也是通過串口與外部Modem通信的。為了讓已有的拔號程序不做修改,像使用外部Modem一樣使用內置卡,就需要內置卡的驅動程序虛擬一個串口設備。又如當前工業界使用的一些串口伺服器,往往有8個或16個甚至更多的串口,以連接多個串口設備,再通過一個網卡直接連入乙太網。與它在同一網路上的計算機就通過乙太網與串口伺服器上掛接的串口設備通信。為了讓計算機中原來使用本地串口的軟體兼容,就需要在計算機上提供虛擬串口驅動。
虛擬串口的設計關鍵在於,該「串口」實現後必須具有與真實串口完全相同的系統調用介面。要做到這點,從已有的串口設備驅動程序上做修改是最佳捷徑。下文就介紹以Windows NT上的串口驅動程序為基礎,開發可運行於Windows NT、Windows 2000、Windows XP的各個版本虛擬串口驅動程序。
串口驅動中使用的幾個鏈表
由於串口是雙工設備,在一個讀請求發出來還沒有完成之前,同時可以發出寫請求,加上在驅動程序層所有I/O請求都要求非同步完成,即前一個請求尚沒有完成,下一個相同的請求可能又來了。為此,串口驅動程序需要使用多個雙向鏈表數據結構來處理各種IRP(I/O Request Packet,I/O請求包)。當收到一個IRP,先判斷是否可立即完成,可以馬上處理並返回,如果不允許則將IRP插在相應鏈表尾,在適當的時候如設備有空閑時處理,這時往往會產生一個硬體中斷,激發DPC(Deferred Procere Call,暫緩過程調用)過程,由DPC處理函數逐個從鏈表頭取出IRP並試著完成它。串口驅動中有以下幾個鏈表和DPC(在serial.h中有定義):
ReadQueue 和 CompleteReadDpc
用於保存Read IRP的鏈表和用於調度的DPC,與DPC對應的處理函數是SerialCompleteRead,它在read.c文件中,該函數的主要任務就是從ReadQueue中提取下一個IRP,並試著完成它。
WriteQueue 和 CompleteWriteDpc
用於保存Write IRP的鏈表和對應的DPC,與DPC對應的函數是SeriaCompleteWrite,它的實現在write.c中,該函數負責從WriteQueue中提取IRP,並試著完成它。
MaskQueue 和 CommWaitDpc
這一對鏈表用於處理Windows串口驅動的一個特性:事件驅動機制。它允許應用程序預設一個事件標志,而後等待與標志對應事件發生。DPC所調用的函數是SerialCompleteWait,它實現在Waitmask.c文件中,該函數也是試著從MaskQueue中提取IRP並完成它。
PurgeQueue
該鏈表與前面幾個稍有不同,它沒有與之相對應的DPC機制,而是在每次收到Purge請求時從PurgeQueue中逐個提取IRP並試著完成,因某種原因不能完成時則插入鏈表。相應的函數是purge.c文件中的SerialStartPurge。
以上機制是串口驅動程序的重要實現方法,在虛擬串口驅動中需要保留,但不同的是,硬體串口驅動中是ISR(中斷服務程序)根據收、發或MODEM中斷來激發相應的DPC,而在虛擬串口驅動中將因實際情況不同會有不同的激發機制。
DriverEntry的實現
DriverEntry是驅動程序的入口函數,相當於應用程序C語言中的main函數,開發一個虛擬串口驅動首先要修改的就是它。它的函數實體在initunlo.c文件中。只是在虛擬串口驅動中由於不與具體的硬體打交道,就不存在硬體資源分析、硬體初始化、判斷其工作狀態等處理,只需要為虛擬串建立設備對象、符號鏈接和初始化數據結構。一個典型函數實現大體如下:
NTSTATUS DriverEntry(IN PDRIVER_OBJECT DriverObject, IN PUNICODE_STRING RegistryPath)
{
/*填寫DriverObject->MajorFunction[]數組*/
/*建立設備對象*/
/*初始化SERIAL_DEVCIE_EXETENSION數據結構*/
Status = IoCreateDevice(DriverObject, sizeof(SERIAL_DEVICE_EXTENSION), &uniNameString, FILE_DEVICE_SERIAL_PORT, 0,TRUE,&deviceObject);
//初始化所有鏈表
InitializeListHead(&extension->ReadQueue);
InitializeListHead(…);
…;
//初始化所有DPC
KeInitializeDpc(&extension->CompleteReadDpc,SerailCompleteRead,extension);
KeInitializeDpc(…);
/*建立符號鏈接*/
SerialSetupExternalNaming(extension);
return Status;
}
SerialRead和SerialCompleteRead的實現
函數SerailRead和SerialCompleteRead決定了對Read IRP的響應策略,它們都存於read.c中。以串口伺服器要用的虛擬串口為例,當串口伺服器收到來自外部數據時將通過網路發至計算機,計算機則產生相應的網路中斷並進行協議數據處理。網路接收線程緩存新收到的數據並激活CompleteReadDpc,從而SerialCompleteReadIrp得到調用,它再調用CompleteReadIrp對每個IRP進行處理。它們的實現大體如下:
NTSTATUS SerialRead(IN PDEVICE_OBJECT DeviceObject,IN PIRP Irp)
{
/*此處略去變數聲明和初始化*/
/*提取IRP中相關的數據*/
stack = IoGetCurrentIrpStackLocation(Irp);
ReadLen = stack->Parameters.Read.Length;
/*先看本地緩沖有數據否?有的話先讀取*/
if(Extension->InCounter > 0 )
{ //注意這里要加鎖,以防數據訪問沖突
KeAcquireSpinLock(&Extension->
ReadBufferLock,&lIrql);
FirstRead = (ReadLen>Extension->
InCounter)? Extension->InCounter: ReadLen;
RtlCopyMemory(Irp->AssociatedIrp.
SystemBuffer,Extension->pInBuffer,FirstRead);
Extension->InCounter -= FirstRead;
ReadLen -= FirstRead;
KeReleaseSpinLock(&Extension->
ReadBufferLock,lIrql);//釋放鎖
}
/*是否已讀到足夠數據?是的話則完成該IRP*/
if( 0 == ReadLen)
{
status=STATUS_SUCCESS;
Irp->IoStatus.Status = status;
Irp->IoStatus.Information = FirstRead;
IoCompleteRequest(Irp,0);
return status;
}
/*沒有則將IRP插入隊列中,通過網路向串口伺服器發出讀數據請求*/
IoMarkIrpPending(Irp);
InsertWaitList(Extension->ReadQueue,Irp);
status = TdiSendAsync(Extension->ComChannel,pAckPacket,PacketLen(pAckPacket),(PVOID)ReadAckComplete,Irp);
/*返回PENDING,表示該IRP尚沒有完成*/
return STATUS_PENDING;
}
Void CompleteReadIrp(IN PSERIAL_DEVICE_EXTENSION extension,IN PIRP Irp,IN PUCHAR pInData,IN ULONG Length )
{
/*此處略去變數聲明和初始化*/
/*讀取新數據*/
ReadLen = (ReadLen > Length)? Length : ReadLen;
if(ReadLen != 0)
{
RtlCopyMemory(pReadAsync->
pReadBuffer,pInData,ReadLen);
pReadAsync->pReadBuffer += ReadLen;
pReadAsync->ReadAlready += ReadLen;
extension->PerfStats.ReceivedCount +=
ReadLen;
}
else
{
/*因為串口伺服器端只有在已經有了相應的數據或超過時間(此時,Length=0)才會發來應答並激活本DPC過程,所以此時已經超時,為了便於結束本IRP,這里有意改變TotalNeedRead,造成接收完畢的假象*/
pReadAsync->TotalNeedRead =
pReadAsync->ReadAlready;
}
if(pReadAsync->TotalNeedRead == pReadAsync->ReadAlready)
{
/*該IRP是否已經接收完畢,是的話則結束該
IRP*/
EndReadIrp(Irp);
/*從ReadQueue中取下一個IRP*/
}
/*本IRP沒有完成也沒有超時,則繼續等待本DPC下次被激活,注意此時要判斷IRP是否被要求取消*/
}
SerialWrite和SerailCompleteWrite的實現
SerialWrite和SerailCompleteWrite決定了Write IRP的實現。在SerialWrite中調用了網路發送函數TdiSendAsync,當該發送完成後將激活CompleteWriteDpc,調度SerialCompleteWrite函數,而它主要就是取出當前的WriteIRP,設置已經發送的數據數量,調用CompleteWriteIrp做該IRP的進一步處理。它們大體如下:
NTSTATUS SerialWrite(IN PDEVICE_OBJECT DeviceObject,IN PIRP Irp)
{
/*此處略去變數聲明和初始化*/
/*從IRP中提取有關數據*/
stack=IoGetCurrentIrpStackLocation(Irp);
SendLen = stack->Parameters.Write.Length;
/*為網路發送和非同步操作分配緩沖,在CompleteWrite中全部數據發送完後釋放*/
pWriteAsync = ExAllocatePool(NonPagedPool,
SendLen+PACKET_HEADER_LEN+sizeof(WRITE_ASYNC));
if(pWriteAsync == NULL)
{
//錯誤處理
}
//保存非同步數據
…
//設置網路發送數據包
BuildDataPacket(pPacket,WRITE,(USHORT)SendLen,pWriteAsync->pWriteBuffer);
/*先將IRP暫時阻塞並插入隊列,在CompleteWrite中完成*/
IoMarkIrpPending(Irp);
InsertWaitList(extension->WriteQueue, Irp);
/*將寫請求和相關數據通過網路發向串口伺服器,由它負責將數據傳到具體串口設備*/
status = TdiSendAsync(Extension->ComChannel,pPacket,PacketLen(pPacket),(PVOID)CompleteWriteIrp,Irp);
//統計數據累加
Extension->PerfStats.TransmittedCount += SendLen;
return STATUS_PENDING;
}
NTSTATUS CompleteWriteIrp(IN PDEVICE_OBJECT deviceobject,IN PIRP pIrp,IN PVOID context)
{
/*此處略去變數聲明和初始化*/
SendLen=pWriteAsync->TotalNeedWrite - pWriteAsync->WroteAlready;
if(SendLen == 0)//全部數據發送完畢
{
EndWaitWriteIrp(pWriteIrp,STATUS_SUCCESS,
pWriteAsync->WroteAlready,pWriteAsync);
//從WriteQueue中取下一個IRP;
}
else //發送剩餘數據
{
if(pWriteIrp->Cancel)
{
//IRP被要求取消,完成WriteIrp
EndWaitWriteIrp(pWriteIrp,STATUS_CANCELLED,
pWriteAsync->WroteAlready,pWriteAsync);
return STATUS_CANCELED;
}
else
{
//再次設置網路數據包並發送
BuildDataPacket(…);
status = TdiSendAsync(…);
//統計數據累加
Extension->PerfStats.TransmittedCount +=
SendLen;
return STATUS_MORE_PROCESSING_REQUIRED;
}
}
}
其他幾個介面函數的實現
除Read/Write外,SerialUnload、SerialCreateOpen、 SerialClose、SerialCleanup、SerailFlush等調用介面是硬體相關性比較弱的介面函數,基本不要修改,直接刪除原來操作硬體的部分即可。復雜一點就是SerialIoControl,該介面函數包含有大量設置、讀取串口硬體狀態的處理,可建立一個本地數據結構隨時保存虛擬串口的當前硬體狀態。同時為了保證串口伺服器端的真實串口狀態和上層軟體要求的一致,需要將所有設置請求通過網路發送到伺服器端,由它負責改變真實硬體的狀態。
② linux打開終端命令linux終端打開命令
linux命令行怎樣打開新的窗口?
在GUI圖形界面下你可以在命令行輸入xterm打開新的窗口
linux怎麼進終端機?
1、在Linux的首頁使用Ctrl+alt+T,打開終端。
2、Linux支持多終端,可以再使用Ctrl+alt+T命令打開多個終端。
3、通過「searchyourcomputer」功能搜索,terminal。
4、輸入「ter」,可以看到,terminal終端出現了,單擊打開。
5、可以滑鼠左鍵terminal,把他拖到到「searchyourcomputer」的下面,然後右鍵固定到「啟動欄」中,就類似Windows的「鎖定到任務欄」這樣就方便下次直接使用。
串口控制台如何打開linux終端控制台?
串口控制台打開linux終端控制台的方法是追加打開命令的行,為了將這些信息顯示在終端上,可以將這些文件中含有echo命令的行追加>TTY。TTY是終端的串口(與/etc/inittab中串口終端行的一樣)。
值得注意的是,這樣做的好處是利用串口終端作為Linux控制台,可以免去額外的鍵盤,顯示卡和顯示器,同時可將Linux主機作為一個任意用途的嵌入式黑匣。
具體的操作方法是,在文件/etc/inittab增加下面一行。
ID:RUNLEVELS:respawn:/sbin/agetty-LSPEEDTTYTERM。
這里:ID=兩字母的標識符,如s1或s2。RUNLEVELS=終端激活的運行級別。SPEED=串口埠速率。TTY=串口的設備名。TERM=TERM環境變數。範例如下s2:12345:respawn:/sbin/agetty-L9600ttyS1vt100,表示串口/dev/ttyS1(COM2)速率為9600bps,終端模式為vt100。
windows如何進入終端?
方法一:WinX菜單
右鍵單擊任務欄上的Windows圖標,打開WinX菜單(快捷鍵:Win+X)
如果你想要提升許可權,請單擊Windows終端(管理員)
方法二:使用搜索打開Windows終端
單擊任務欄上的搜索圖標。
鍵入WindowsTerminal並單擊打開。
方法三:開始菜單
單擊任務欄上的Windows圖標或使用鍵盤上的Windows鍵打開開始菜單。
單擊「所有應用」按鈕。
向下滾動,直到到達「W」應用程序並找到WindowsTerminal,單擊它。
linux怎麼用命令打開編輯器?
1、打開終端。點擊菜單。
2、然後找到終端程序。程序的圖標是一個黑色的方框,上面有一個白色的「>_」。點擊它。一般可以從菜單窗口的左側欄中找到「終端」程序。
3、在終端程序中輸入vi文件名.txt。命令中的「vi」部分是選擇「Vim」文本編輯器來打開並編輯文件。用目標文件名來代替命令中的「文件名」。例如文件名是「tamins」,請輸入vitamins.txt,按下Enter鍵。
4、關閉文件時在終端中輸入:q,按下回車鍵。這樣會退出Vim程序,返回到終端主界面。