VC+中進(jìn)程與多進(jìn)程管理的實(shí)現(xiàn)方法 C+/VC
進(jìn)程
進(jìn)程是當(dāng)前操作系統(tǒng)下一個被加載到內(nèi)存的、正在運(yùn)行的應(yīng)用程序的實(shí)例。每一個進(jìn)程都是由內(nèi)核對象和地址空間所組成的,內(nèi)核對象可以讓系統(tǒng)在其內(nèi)存放有關(guān)進(jìn)程的統(tǒng)計(jì)信息并使系統(tǒng)能夠以此來管理進(jìn)程,而地址空間則包括了所有程序模塊的代碼和數(shù)據(jù)以及線程堆棧、堆分配空間等動態(tài)分配的空間。進(jìn)程僅僅是一個存在,是不能獨(dú)自完成任何操作的,必須擁有至少一個在其環(huán)境下運(yùn)行的線程,并由其負(fù)責(zé)執(zhí)行在進(jìn)程地址空間內(nèi)的代碼。在進(jìn)程啟動的同時即同時啟動了一個線程,該線程被稱作主線程或是執(zhí)行線程,由此線程可以繼續(xù)創(chuàng)建子線程。如果主線程退出,那么進(jìn)程也就沒有存在的可能了,系統(tǒng)將自動撤消該進(jìn)程并完成對其地址空間的釋放。
加載到進(jìn)程地址空間的每一個可執(zhí)行文件或動態(tài)鏈接庫文件的映象都會被分配一個與之相關(guān)聯(lián)的全局唯一的實(shí)例句柄(hinstance)。該實(shí)例句柄實(shí)際是一個記錄有進(jìn)程加載位置的基本內(nèi)存地址。進(jìn)程的實(shí)例句柄在程序入口函數(shù)winmain()中通過第一個參數(shù)hinstance hinstexe傳遞,其實(shí)際值即為進(jìn)程所使用的基本地址空間的地址。對于vc++鏈接程序所鏈接產(chǎn)生的程序,其默認(rèn)的基本地址空間地址為0x00400000,如沒有必要一般不要修改該值。在程序中,可以通過getmodulehandle()函數(shù)得到指定模塊所使用的基本地址空間。
子進(jìn)程的創(chuàng)建
進(jìn)程的創(chuàng)建通過createprocess()函數(shù)來實(shí)現(xiàn),createprocess()通過創(chuàng)建一個新的進(jìn)程及在其地址空間內(nèi)運(yùn)行的主線程來啟動并運(yùn)行一個新的程序。具體的,在執(zhí)行createprocess()函數(shù)時,首先由操作系統(tǒng)負(fù)責(zé)創(chuàng)建一個進(jìn)程內(nèi)核對象,初始化計(jì)數(shù)為1,并立即為新進(jìn)程創(chuàng)建一塊虛擬地址空間。隨后將可執(zhí)行文件或其他任何必要的動態(tài)鏈接庫文件的代碼和數(shù)據(jù)裝載到該地址空間中。在創(chuàng)建主線程時,也是首先由系統(tǒng)負(fù)責(zé)創(chuàng)建一個線程內(nèi)核對象,并初始化為1。最后啟動主線程并執(zhí)行進(jìn)程的入口函數(shù)winmain(),完成對進(jìn)程和執(zhí)行線程的創(chuàng)建。
createprocess()函數(shù)的原型聲明如下:
bool createprocess(
lpctstr lpapplicationname, // 可執(zhí)行模塊名
lptstr lpcommandline, // 命令行字符串
lpsecurity_attributes lpprocessattributes, // 進(jìn)程的安全屬性
lpsecurity_attributes lpthreadattributes, // 線程的安全屬性
bool binherithandles, // 句柄繼承標(biāo)志
dword dwcreationflags, // 創(chuàng)建標(biāo)志
lpvoid lpenvironment, // 指向新的環(huán)境塊的指針
lpctstr lpcurrentdirectory, // 指向當(dāng)前目錄名的指針
lpstartupinfo lpstartupinfo, // 指向啟動信息結(jié)構(gòu)的指針
lpprocess_information lpprocessinformation // 指向進(jìn)程信息結(jié)構(gòu)的指針
);
在程序設(shè)計(jì)時,某一個具體的功能模塊可以通過函數(shù)或是線程等不同的形式來實(shí)現(xiàn)。對于同一進(jìn)程而言,這些函數(shù)、線程都是存在于同一個地址空間下的,而且在執(zhí)行時,大多只對與其相關(guān)的一些數(shù)據(jù)進(jìn)行處理。如果算法存在某種錯誤,將有可能破壞與其同處一個地址空間的其他一些重要內(nèi)容,這將造成比較嚴(yán)重的后果。為保護(hù)地址空間中的內(nèi)容可以考慮將那些需要對地址空間中的數(shù)據(jù)進(jìn)行訪問的操作部分放到另外一個進(jìn)程的地址空間中運(yùn)行,并且只允許其訪問原進(jìn)程地址空間中的相關(guān)數(shù)據(jù)。具體的,可在進(jìn)程中通過createprocess()函數(shù)去創(chuàng)建一個子進(jìn)程,子進(jìn)程在全部處理過程中只對父進(jìn)程地址空間中的相關(guān)數(shù)據(jù)進(jìn)行訪問,從而可以保護(hù)父進(jìn)程地址空間中與當(dāng)前子進(jìn)程執(zhí)行任務(wù)無關(guān)的全部數(shù)據(jù)。對于這種情況,子進(jìn)程所體現(xiàn)出來的作用同函數(shù)和線程比較相似,可以看成是父進(jìn)程在運(yùn)行期間的一個過程。為此,需要由父進(jìn)程來掌握子進(jìn)程的啟動、執(zhí)行和退出。下面這段代碼即展示了此過程:
// 臨時變量
cstring scommandline;
char cwindowsdirectory[max_path];
char ccommandline[max_path];
dword dwexitcode;
process_information pi;
startupinfo si = {sizeof(si)};
// 得到windows目錄
getwindowsdirectory(cwindowsdirectory, max_path);
// 啟動"記事本"程序的命令行
scommandline = cstring(cwindowsdirectory) + "\notepad.exe";
::strcpy(ccommandline, scommandline);
// 啟動"記事本"作為子進(jìn)程
bool ret = createprocess(null, ccommandline, null, null, false, 0, null, null, &si, &pi);
if (ret) {
// 關(guān)閉子進(jìn)程的主線程句柄
closehandle(pi.hthread);
// 等待子進(jìn)程的退出
waitforsingleobject(pi.hprocess, infinite);
// 獲取子進(jìn)程的退出碼
getexitcodeprocess(pi.hprocess, &dwexitcode);
// 關(guān)閉子進(jìn)程句柄
closehandle(pi.hprocess);
}
此段代碼首先通過createprocess()創(chuàng)建windows自帶的“記事本”程序?yàn)樽舆M(jìn)程,子進(jìn)程啟動后父進(jìn)程通過waitforsingleobject()函數(shù)等待其執(zhí)行的結(jié)束,在子進(jìn)程沒有退出前父進(jìn)程是一直處于阻塞狀態(tài)的,這里子進(jìn)程的作用同單線程中的函數(shù)類似。一旦子進(jìn)程退出,waitforsingleobject()函數(shù)所等待的pi.hprocess對象將得到通知,父進(jìn)程將得以繼續(xù),如有必要可以通過getexitcodeprocess()來獲取子進(jìn)程的退出代碼。
相比而言,更多的情況是父進(jìn)程在啟動完子進(jìn)程后就再不與其進(jìn)行任何數(shù)據(jù)交換和通訊,由其創(chuàng)建的子進(jìn)程的執(zhí)行成功與否均與父進(jìn)程無關(guān)。許多大型軟件在設(shè)計(jì)時也多采用了這類思想,將某些功能完全通過獨(dú)立的應(yīng)用程序來完成,當(dāng)需要執(zhí)行某操作時只要通過主程序啟動相應(yīng)的子進(jìn)程即可,具體的處理工作均由子進(jìn)程去完成。這類子進(jìn)程的創(chuàng)建過程更為簡單,例如對于上面那段代碼只需去除對子進(jìn)程句柄pi.hprocess的等待即可:
bool ret = createprocess(null, ccommandline, null, null, false, 0, null, null, &si, &pi);
if (ret) {
// 關(guān)閉子進(jìn)程的主線程句柄
closehandle(pi.hthread);
// 關(guān)閉子進(jìn)程句柄
closehandle(pi.hprocess);
}
可以通過dwcreationflags參數(shù)在創(chuàng)建進(jìn)程時設(shè)置子進(jìn)程的優(yōu)先級。前面的示例代碼在創(chuàng)建子進(jìn)程時使用的均是默認(rèn)的優(yōu)先級,如果要將優(yōu)先級設(shè)置為高,可以修改如下:
bool ret = createprocess(null, ccommandline, null, null, false, high_priority_class, null, null, &si, &pi);
如果在進(jìn)程創(chuàng)建時沒有特別設(shè)置優(yōu)先級,可以通過setpriorityclass()函數(shù)來動態(tài)設(shè)定,該函數(shù)需要待操作進(jìn)程的句柄和優(yōu)先級標(biāo)識符作為入口參數(shù),函數(shù)原型為:
bool setpriorityclass(handle hprocess, dword dwpriorityclass);
對于前面沒有設(shè)定優(yōu)先級的例子代碼,可以在子進(jìn)程啟動后由父進(jìn)程來動態(tài)改變其優(yōu)先級設(shè)置:
setpriorityclass(pi.hprocess, high_priority_class);
或是由子進(jìn)程在其啟動后自行改變優(yōu)先級設(shè)置,需要注意的是這時進(jìn)程句柄應(yīng)設(shè)置為子進(jìn)程自身的句柄,可通過getcurrentprocess()函數(shù)來獲?。?/P>
handle hprocess = getcurrentprocess();
setpriorityclass(hprocess, high_priority_class);
進(jìn)程的互斥運(yùn)行
正常情況下,一個進(jìn)程的運(yùn)行一般是不會影響到其他正在運(yùn)行的進(jìn)程的。但是對于某些有特殊要求的如以獨(dú)占方式使用串行口等硬件設(shè)備的程序就要求在其進(jìn)程運(yùn)行期間不允許其他試圖使用此端口設(shè)備的程序運(yùn)行的,而且此類程序通常也不允許運(yùn)行同一個程序的多個實(shí)例。這就引出了進(jìn)程互斥的問題。
實(shí)現(xiàn)進(jìn)程互斥的核心思想比較簡單:進(jìn)程在啟動時首先檢查當(dāng)前系統(tǒng)是否已經(jīng)存在有此進(jìn)程的實(shí)例,如果沒有,進(jìn)程將成功創(chuàng)建并設(shè)置標(biāo)識實(shí)例已經(jīng)存在的標(biāo)記。此后再創(chuàng)建進(jìn)程時將會通過該標(biāo)記而知曉其實(shí)例已經(jīng)存在,從而保證進(jìn)程在系統(tǒng)中只能存在一個實(shí)例。具體可以采取內(nèi)存映射文件、有名事件量、有名互斥量以及全局共享變量等多種方法來實(shí)現(xiàn)。下面就分別對其中具有代表性的有名互斥量和全局共享變量這兩種方法進(jìn)行介紹:
// 創(chuàng)建互斥量
handle m_hmutex = createmutex(null, false, "sample07");
// 檢查錯誤代碼
if (getlasterror() == error_already_exists) {
// 如果已有互斥量存在則釋放句柄并復(fù)位互斥量
closehandle(m_hmutex);
m_hmutex = null;
// 程序退出
return false;
}
上面這段代碼演示了有名互斥量在進(jìn)程互斥中的用法。代碼的核心是createmutex()對有名互斥量的創(chuàng)建。createmutex()函數(shù)可用來創(chuàng)建一個有名或無名的互斥量對象,其函數(shù)原型為:
handle createmutex(
lpsecurity_attributes lpmutexattributes, // 指向安全屬性的指針
bool binitialowner, // 初始化互斥對象的所有者
lpctstr lpname // 指向互斥對象名的指針
);
如果函數(shù)成功執(zhí)行,將返回一個互斥量對象的句柄。如果在createmutex()執(zhí)行前已經(jīng)存在有相同名字的互斥量,函數(shù)將返回這個已經(jīng)存在互斥量的句柄,并且可以通過getlasterror()得到錯誤代碼error_already_exist??梢?,通過對錯誤代碼error_already_exist的檢測可以實(shí)現(xiàn)createmutex()對進(jìn)程的互斥。
使用全局共享變量的方法則主要是在mfc框架程序中通過編譯器來實(shí)現(xiàn)的。通過#pragma data_seg預(yù)編譯指令創(chuàng)建一個新節(jié),在此節(jié)中可用volatile關(guān)鍵字定義一個變量,而且必須對其進(jìn)行初始化。volatile關(guān)鍵字指定了變量可以為外部進(jìn)程訪問。最后,為了使該變量能夠在進(jìn)程互斥過程中發(fā)揮作用,還要將其設(shè)置為共享變量,同時允許具有讀、寫訪問權(quán)限。這可以通過#pragma comment預(yù)編譯指令來通知編譯器。下面給出使用了全局變量的進(jìn)程互斥代碼清單:
#pragma data_seg("shared")
int volatile g_lappinstance =0;
#pragma data_seg()
#pragma comment(linker,"/section:shared,rws")
……
if(++g_lappinstance>1)
return false;
此段代碼的作用是在進(jìn)程啟動時對全局共享變量g_nappinstancd 加1 ,如果發(fā)現(xiàn)其值大于1,那么就返回false以通知進(jìn)程結(jié)束。這里需要特別指出的是,為了使以上兩段代碼能夠真正起到對進(jìn)程互斥的作用,必須將其放置在應(yīng)用程序的入口代碼處,即應(yīng)用程序類的初始化實(shí)例函數(shù)initinstance()的開始處。
結(jié)束進(jìn)程
進(jìn)程只是提供了一段地址空間和內(nèi)核對象,其運(yùn)行是通過在其地址空間內(nèi)的主線程來體現(xiàn)的。當(dāng)主線程的進(jìn)入點(diǎn)函數(shù)返回時,進(jìn)程也就隨之結(jié)束。這種進(jìn)程的終止方式是進(jìn)程的正常退出,進(jìn)程中的所有線程資源都能夠得到正確的清除。除了這種進(jìn)程的正常推出方式外,有時還需要在程序中通過代碼來強(qiáng)制結(jié)束本進(jìn)程或其他進(jìn)程的運(yùn)行。exitprocess()函數(shù)即可在進(jìn)程中的某個線程中使用,并將立即終止本進(jìn)程的運(yùn)行。exitprocess()函數(shù)原型為:
void exitprocess(uint uexitcode);
其參數(shù)uexitcode為進(jìn)程設(shè)置了退出代碼。該函數(shù)具有強(qiáng)制性,在執(zhí)行完畢后進(jìn)程即已經(jīng)被結(jié)束,因此位于其后的任何代碼將不能被執(zhí)行。雖然exitprocess()函數(shù)可以在結(jié)束進(jìn)程的同時通知與其相關(guān)聯(lián)的動態(tài)鏈接庫,但是由于它的這種執(zhí)行的強(qiáng)制性,使得exitprocess()函數(shù)在使用上將存在有安全隱患。例如,如果在程序調(diào)用exitprocess()函數(shù)之前曾用new操作符申請過一段內(nèi)存,那么將會由于exitprocess()函數(shù)的強(qiáng)制性而無法通過delete操作符將其釋放,從而造成內(nèi)存泄漏。有鑒于exitprocess()函數(shù)的強(qiáng)制性和不安全性,在使用時一定要引起注意。
exitprocess()只能強(qiáng)制執(zhí)行本進(jìn)程的退出,如果要在一個進(jìn)程中強(qiáng)制結(jié)束其他的進(jìn)程就要用terminateprocess()來實(shí)現(xiàn)。與exitprocess()不同,terminateprocess()函數(shù)執(zhí)行后,被終止的進(jìn)程是不會得到任何關(guān)于程序退出的通知的。也就是說,被終止的進(jìn)程是無法在結(jié)束運(yùn)行前進(jìn)行退出前的收尾工作的。所以,通常只有在其他任何方法都無法迫使進(jìn)程退出時才會考慮使用terminateprocess()去強(qiáng)制結(jié)束進(jìn)程的。下面給出terminateprocess()的函數(shù)原型:
bool terminateprocess(handle hprocess, uint uexitcode);
參數(shù)hprocess和uexitcode分別為進(jìn)程句柄和退出代碼。如果被結(jié)束的是本進(jìn)程,可以通過getcurrentprocess()獲取到句柄。terminateprocess()是異步執(zhí)行的,在調(diào)用返回后并不能確定被終止進(jìn)程是否已經(jīng)真的退出,如果調(diào)用terminateprocess()的進(jìn)程對此細(xì)節(jié)關(guān)心,可以通過waitforsingleobject()來等待進(jìn)程的真正結(jié)束。
小結(jié)
多進(jìn)程是多任務(wù)管理中的重要內(nèi)容,文中上述部分對其基本概念和主要的技術(shù)如子進(jìn)程的創(chuàng)建與結(jié)束、進(jìn)程間的互斥運(yùn)行等做了較詳細(xì)的介紹。通過本文讀者應(yīng)能對多進(jìn)程管理有一個初步的認(rèn)識。
來源:博士0次