㈠ c語言多進程編程
多進程這個詞用得比較少,聽過來有點不熟悉。你這個程序在linux下應該很容易實行,就是個進程間通信的問題,管道、消息隊列、共享內存都可以,可以找找相關資料。昨天失言不好意思。
三個源文件分別為1.c、2.c、3.c一個頭文件share.h。
share.h:
//共享的內存,兩個數組
typedef struct{
int a[2];
int b[2];
int id;
}share_use;
1.c:
#include<unistd.h>
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<sys/types.h>
#include<sys/ipc.h>
#include<sys/shm.h>
#include"share.h"
int main(){
void *shared_memory = (void *)0;
share_use *share_stuff;
int shmid;
shmid=shmget((key_t)1234,sizeof(share_use),0666|IPC_CREAT);//創建共享內存
if(shmid==-1){
fprintf(stderr,"共享內存創建失敗!\n");
exit(1);
}
shared_memory = shmat(shmid, (void *)0,0);//讓進程可以訪問共享內存
if(shared_memory == (void *)-1){
fprintf(stderr,"啟用共享內存失敗!\n)";
exit(1);
}
printf("Memory attached at %X\n",(int)shared_memory);
share_stuff = (share_use *)shared_memory;
share_stuff->id=0;
share_stuff->a[0]=1;
share_stuff->a[1]=2;
while(1){
if(share_stuff->id)
exit(0);
sleep(10);
}
}
2.c:
#include<unistd.h>
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<sys/types.h>
#include<sys/ipc.h>
#include<sys/shm.h>
#include"share.h"
int main(){
void *shared_memory = (void *)0;
share_use *share_stuff;
int shmid;
shmid=shmget((key_t)1234,sizeof(share_use),0666|IPC_CREAT);//創建共享內存
if(shmid==-1){
fprintf(stderr,"共享內存創建失敗!\n");
exit(1);
}
shared_memory = shmat(shmid, (void *)0,0);//讓進程可以訪問共享內存
if(shared_memory == (void *)-1){
fprintf(stderr,"啟用共享內存失敗!\n");
exit(1);
}
printf("Memory attached at %X\n",(int)shared_memory);
share_stuff = (share_use *)shared_memory;
share_stuff->b[0]=share_stuff->a[0]*100;
share_stuff->b[1]=share_stuff->a[1]*100;
while(1)
{
if(share_stuff->id)
exit(0);
sleep(10);
}
}
3.c:
#include<unistd.h>
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<sys/types.h>
#include<sys/ipc.h>
#include<sys/shm.h>
#include"share.h"
int main(){
void *shared_memory = (void *)0;
share_use *share_stuff;
int shmid;
shmid=shmget((key_t)1234,sizeof(share_use),0666|IPC_CREAT);//創建共享內存
if(shmid==-1){
fprintf(stderr,"共享內存創建失敗!\n");
exit(1);
}
shared_memory = shmat(shmid, (void *)0,0);//讓進程可以訪問共享內存
if(shared_memory == (void *)-1){
fprintf(stderr,"啟用共享內存失敗!\n");
exit(1);
}
printf("Memory attached at %X\n",(int)shared_memory);
share_stuff = (share_use *)shared_memory;
printf("共享內存中有元素:%d , %d",share_stuff->b[0],share_stuff->b[1]);
share_stuff->id=1;
return 0;
}
linux或unix環境下編譯
㈡ linux簡單的C語言程序,多進程,為什麼存在死循環的時候不列印字元死循環明明在列印語句的後面呀
流來不及刷新,不是你列印就立刻出現再終端的,它需要一個刷新過程,而你的死循環讓它沒有時間刷新
㈢ 使用C語言編程實現:父進程創建二個子進程,三個進程各自列印出其進程
#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
int main()
{
int i=2;
int pid=0;
printf("father id=%ld ",getpid());
while(i-->0){
pid = fork();
if(pid==0){
printf("son id=%ld ",getpid());
return 0;
}
}
return 0;
}
㈣ 《C語言實戰105例》pdf下載在線閱讀全文,求百度網盤雲資源
《C語言實戰105例》網路網盤pdf最新全集下載:
鏈接:https://pan..com/s/1SUsq3lJF_9SOjSTGnuiUFw
簡介:C語言實戰105例共匯集105個實例,內容循序漸進,通過實例進許可證C語言編程。全書分為8篇,包括基礎篇、數值計算與數據結構篇、文本屏幕與文件操作篇、病毒與安全篇、圖形篇、系統篇、游戲篇、綜合篇,基本涵蓋了目前C語言編程的各個方面。
㈤ )用C語言(或其它語言,如Java)編程實現對N個進程採用某種進程調度演算法(如動態優先權調度
公眾:類PrivilegeProcess {
公共靜態無效的主要(字串[] args){
MyQueue的MyQueue的新MyQueue的();/ /聲明隊列
印刷電路板[PCB = {新的PCB(001 ,8,1),新的PCB(002,7,9),新的PCB(003,3,8),新的PCB(004,1,7),新的PCB(005,7,4)};
> PCB段=新的PCB();
(INT I = 0; <pcb.length; + +){/ /初始化先進行排序,選擇排序這里使用的是高優先順序的一線隊
(J =我; <pcb.length; J + +){
(PCB [I]。特權<PCB [J]。特權){
段= PCB [1];
PCB [I] = PCB [J];
PCB [J] =段;
}
}
}
體系。通過out.println(「入隊後第一時間的進首粗塌程的順序:」);
(INT I = 0; <pcb.length; + +){
的System.out調用println(第一次入隊#程序名稱:「+ PCB [我]。名稱+ totaltime:」+ PCB [I]。totaltime +「的」特權「+ PCB [我]。特權); }
();
myqueue.start(PCB);
}
}
類MyQueue的{
INT指數= 0;
PCB [] PC =新的PCB [5];
PCB [] PC1 =新的PCB [4];
PCB溫度=新的PCB() BR />公共無效排隊(PCB工藝){/ /者圓排隊演算法
(指數== 5){
(「出界!」);
返回
}
PC [索引] =進程;
指數+ +;
}
公共:PCB DEQUEUE(){/ /出隊演算法(索引== 0)
返回空;
(INT I = 0; <pc1.length; + +){
PC1 [I] = PC [ +1];
}
指數 -
溫度= PC [0];
(INT I = 0; <pc1.length; + +){ BR /> PC [I] = PC1 [I];
}
回報條件;
}
公共無效啟動(PCB [] PC){/ /進程表演算法
(PC [0]。isNotFinish ==真| | PC [1 isNotFinish ==真| | PC [2 isNotFinish ==真| | PC [3]。時isNotFinish ==真| | PC [4]。isNotFinish ==){
/ / *註:| |運算符都是假的,所有的表達式結果為假,否則真
(INT I = 0; <PC長度; + +){
PC [I]。運行(這一點); />} 的System.out.println();
(INT I = 0; <pc.length; + +){/ /處理每個運行一次運行的時間片的長度重新排序優先一旦
(J =我; <pc.length; J + +){
如果(PC [I]特權<PC [J]。特權){
溫度= PC [I];
PC [I] = PC [J];
PC [J] =溫度;
}
}
}
}
}
}
類PCB {/ /聲明過程級
和int名,totaltime ,運行時特權;
布爾isNotFinish的;
公眾PCB(){
}
公開PCB(名稱,詮釋totaltime特權){
this.name =的名稱;/ /進程名
this.totaltime = totaltime ;/ /
this.privilege =特權;/ /總時間優先 this.runtime = 2 ;/ /時間片值是2
this.isNotFinish =真;/ /是否執行凳畝完成
(「初始值:程序名稱:」+名+「totaltime:」+ totaltime +「特權」+特權);
System.out的。調用println();
}
MyQueue的MQ公共無效的run(){/ /處理的基礎上實施的時間片演算法
(totalTime> 1){ totaltime =運行;/ /總時間大於1,總時間=總時間 - 時間片
特權 -
(「程序名稱:」+姓名+「 remaintime:「+ +」特權「+特權); totaltime
的} else if(totaltime == 1){
totaltime - ;/ /總時間為1時,執行時間為1
>特權 -
(「程序名稱:」+姓名+「remaintime:」+ totaltime +「特權」+特權);
}其他{
isNotFinish =假;/ / 0,將isNotFinish標志設置為假
}
如果(isNotFinish ==真){br mq.deQueue();
mq.enQueue(本);
}
}
}
㈥ 怎麼在win7環境下用C語言寫多進程
可以用code::blocks配MinGW編譯器,無論是初學姿友者還是高手,都適用。配置簡單,易於操作。
Code::Blocks 是一個開放源碼的全功能的跨平台C/C++集成開發環境。 Code::Blocks是開放源碼軟體。Code::Blocks由純粹的C++語言開發完成閉冊肆,它使用了著名的圖形界面庫wxWidgets(2.6.2 unicode)版。對於追求完美的C++程序員,再也不必忍受Eclipse的緩慢,再也不必忍受VS.NET的龐大和轎轎高昂的價格。
㈦ 單片機c語言編程100個實例
51單片機C語言編程實例 基礎知識:51單片機編程基礎 單片機的外部結構: 1. DIP40雙列直插; 2. P0,P1,P2,P3四個8位準雙向I/O引腳;(作為I/O輸入時,要先輸出高電平) 3. 電源VCC(PIN40)和地線GND(PIN20); 4. 高電平復位RESET(PIN9);(10uF電容接VCC與RESET,即可實現上電復位) 5. 內置振盪電路,外部只要接晶體至X1(PIN18)和X0(PIN19);(頻率為主頻的12倍) 6. 程序配置EA(PIN31)接高電平VCC;(運行單片機內部ROM中的程序) 7. P3支持第二功能:RXD、TXD、INT0、INT1、T0、T1 單片機內部I/O部件:(所為學習單片機,實際上就是編程式控制制以下I/O部件,完成指定任務) 1. 四個8位通用I/O埠,對應引腳P0、P1、P2和P3; 2. 兩個16位定時計數器;(TMOD,TCON,TL0,TH0,TL1,TH1) 3. 一個串列通信介面;(SCON,SBUF) 4. 一個中斷控制器;(IE,IP) 針對AT89C52單片機,頭文件AT89x52.h給出了SFR特殊功能寄存器所有埠的定義。 C語言編程基礎: 1. 十六進製表示位元組0x5a:二進制為01011010B;0x6E為01101110。 2. 如果將一個16位二進數賦給一個8位的位元組變數,則自動截斷為低8位,而丟掉高8位。 3. ++var表示對變數var先增一;var—表示對變數後減一。 4. x |= 0x0f;表示為 x = x | 0x0f; 5. TMOD = ( TMOD & 0xf0 ) | 0x05;表示給變數TMOD的低四位賦值0x5,而不改變TMOD的高四位。 6. While( 1 ); 表示無限執行該語句,即死循環。語句後的分號表示空循環體,也就是{;} 在某引腳輸出高電平的編程方法:(比如P1.3(PIN4)引腳) 代碼 1. #include <AT89x52.h> //該頭文檔中有單片機內部資源的符號化定義,其中包含P1.3 2. void main( void ) //void 表示沒有輸入參數,也沒有函數返值,這入單片機運行的復位入口 3. { 4. P1_3 = 1; //給P1_3賦值1,引腳P1.3就能輸出高電平VCC 5. While( 1 ); //死循環,相當 LOOP: goto LOOP; 6. } 注意:P0的每個引腳要輸出高電平時,必須外接上拉電阻(如4K7)至VCC電源。 在某引腳輸出低電平的編程方法:(比如P2.7引腳) 代碼 1. #include <AT89x52.h> //該頭文檔中有單片機內部資源的符號化定義,其中包含P2.7 2. void main( void ) //void 表示沒有輸入參數,也沒有函數返值,這入單片機運行的復位入口 3. { 4. P2_7 = 0; //給P2_7賦值0,引腳P2.7就能輸出低電平GND 5. While( 1 ); //死循環,相當 LOOP: goto LOOP; 6. } 在某引腳輸出方波編程方法:(比如P3.1引腳) 代碼 1. #include <AT89x52.h> //該頭文檔中有單片機內部資源的符號化定義,其中包含P3.1 2. void main( void ) //void 表示沒有輸入參數,也沒有函數返值,這入單片機運行的復位入口 3. { 4. While( 1 ) //非零表示真,如果為真則執行下面循環體的語句 5. { 6. P3_1 = 1; //給P3_1賦值1,引腳P3.1就能輸出高電平VCC 7. P3_1 = 0; //給P3_1賦值0,引腳P3.1就能輸出低電平GND 8. } //由於一直為真,所以不斷輸出高、低、高、低……,從而形成方波 9. } 將某引腳的輸入電平取反後,從另一個引腳輸出:( 比如 P0.4 = NOT( P1.1) ) 代碼 1. #include <AT89x52.h> //該頭文檔中有單片機內部資源的符號化定義,其中包含P0.4和P1.1 2. void main( void ) //void 表示沒有輸入參數,也沒有函數返值,這入單片機運行的復位入口 3. { 4. P1_1 = 1; //初始化。P1.1作為輸入,必須輸出高電平 5. While( 1 ) //非零表示真,如果為真則執行下面循環體的語句 6. { 7. if( P1_1 == 1 ) //讀取P1.1,就是認為P1.1為輸入,如果P1.1輸入高電平VCC 8. { P0_4 = 0; } //給P0_4賦值0,引腳P0.4就能輸出低電平GND 2 51單片機C語言編程實例 9. else //否則P1.1輸入為低電平GND 10. //{ P0_4 = 0; } //給P0_4賦值0,引腳P0.4就能輸出低電平GND 11. { P0_4 = 1; } //給P0_4賦值1,引腳P0.4就能輸出高電平VCC 12. } //由於一直為真,所以不斷根據P1.1的輸入情況,改變P0.4的輸出電平 13. } 將某埠8個引腳輸入電平,低四位取反後,從另一個埠8個引腳輸出:( 比如 P2 = NOT( P3 ) ) 代碼 1. #include <AT89x52.h> //該頭文檔中有單片機內部資源的符號化定義,其中包含P2和P3 2. void main( void ) //void 表示沒有輸入參數,也沒有函數返值,這入單片機運行的復位入口 3. { 4. P3 = 0xff; //初始化。P3作為輸入,必須輸出高電平,同時給P3口的8個引腳輸出高電平 5. While( 1 ) //非零表示真,如果為真則執行下面循環體的語句 6. { //取反的方法是異或1,而不取反的方法則是異或0 7. P2 = P3^0x0f //讀取P3,就是認為P3為輸入,低四位異或者1,即取反,然後輸出 8. } //由於一直為真,所以不斷將P3取反輸出到P2 9. } 注意:一個位元組的8位D7、D6至D0,分別輸出到P3.7、P3.6至P3.0,比如P3=0x0f,則P3.7、P3.6、P3.5、P3.4四個引腳都輸出低電平,而P3.3、P3.2、P3.1、P3.0四個引腳都輸出高電平。同樣,輸入一個埠P2,即是將P2.7、P2.6至P2.0,讀入到一個位元組的8位D7、D6至D0。 第一節:單數碼管按鍵顯示 單片機最小系統的硬體原理接線圖: 1. 接電源:VCC(PIN40)、GND(PIN20)。加接退耦電容0.1uF 2. 接晶體:X1(PIN18)、X2(PIN19)。注意標出晶體頻率(選用12MHz),還有輔助電容30pF 3. 接復位:RES(PIN9)。接上電復位電路,以及手動復位電路,分析復位工作原理 4. 接配置:EA(PIN31)。說明原因。 發光二極的控制:單片機I/O輸出 將一發光二極體LED的正極(陽極)接P1.1,LED的負極(陰極)接地GND。只要P1.1輸出高電平VCC,LED就正向導通(導通時LED上的壓降大於1V),有電流流過LED,至發LED發亮。實際上由於P1.1高電平輸出電阻為10K,起到輸出限流的作用,所以流過LED的電流小於(5V-1V)/10K = 0.4mA。只要P1.1輸出低電平GND,實際小於0.3V,LED就不能導通,結果LED不亮。 開關雙鍵的輸入:輸入先輸出高 一個按鍵KEY_ON接在P1.6與GND之間,另一個按鍵KEY_OFF接P1.7與GND之間,按KEY_ON後LED亮,按KEY_OFF後LED滅。同時按下LED半亮,LED保持後松開鍵的狀態,即ON亮OFF滅。 代碼 1. #include <at89x52.h> 2. #define LED P1^1 //用符號LED代替P1_1 3. #define KEY_ON P1^6 //用符號KEY_ON代替P1_6 4. #define KEY_OFF P1^7 //用符號KEY_OFF代替P1_7 5. void main( void ) //單片機復位後的執行入口,void表示空,無輸入參數,無返回值 6. { 7. KEY_ON = 1; //作為輸入,首先輸出高,接下KEY_ON,P1.6則接地為0,否則輸入為1 8. KEY_OFF = 1; //作為輸入,首先輸出高,接下KEY_OFF,P1.7則接地為0,否則輸入為1 9. While( 1 ) //永遠為真,所以永遠循環執行如下括弧內所有語句 10. { 11. if( KEY_ON==0 ) LED=1; //是KEY_ON接下,所示P1.1輸出高,LED亮 12. if( KEY_OFF==0 ) LED=0; //是KEY_OFF接下,所示P1.1輸出低,LED滅 13. } //松開鍵後,都不給LED賦值,所以LED保持最後按鍵狀態。 14. //同時按下時,LED不斷亮滅,各佔一半時間,交替頻率很快,由於人眼慣性,看上去為半亮態 15. } 數碼管的接法和驅動原理 一支七段數碼管實際由8個發光二極體構成,其中7個組形構成數字8的七段筆畫,所以稱為七段數碼管,而餘下的1個發光二極體作為小數點。作為習慣,分別給8個發光二極體標上記號:a,b,c,d,e,f,g,h。對應8的頂上一畫,按順時針方向排,中間一畫為g,小數點為h。 我們通常又將各二極與一個位元組的8位對應,a(D0),b(D1),c(D2),d(D3),e(D4),f(D5),g(D6),h(D7),相應8個發光二極體正好與單片機一個埠Pn的8個引腳連接,這樣單片機就可以通過引腳輸出高低電平控制8個發光二極的亮與滅,從而顯示各種數字和符號;對應位元組,引腳接法為:a(Pn.0),b(Pn.1),c(Pn.2),d(Pn.3),e(Pn.4),f(Pn.5),g(Pn.6),h(Pn.7)。 如果將8個發光二極體的負極(陰極)內接在一起,作為數碼管的一個引腳,這種數碼管則被稱為共陰數碼管,共同的引腳則稱為共陰極,8個正極則為段極。否則,如果是將正極(陽極)內接在一起引出的,則稱為共陽數碼管,共同的引腳則稱為共陽極,8個負極則為段極。 以單支共陰數碼管為例,可將段極接到某埠Pn,共陰極接GND,則可編寫出對應十六進制碼的七段碼表位元組數據
㈧ 請問下面這個實例用C語言程序怎樣編程
#include <stdio.h>
int main() {
int score;
char grade;
printf("請輸入成績:");
scanf("%d", &score);
switch (score / 10) {
case 10:
case 9:
grade = 'A';
break;
case 8:
grade = 'B';
break;
case 7:
grade = 'C';
break;
case 6:
grade = 'D';
break;
default:
grade = 'E'坦孫;
break;
}
printf("成績等級為:%c\n", grade);
return 0;
}
在程序中,先定義了一個整型變數 score 和一個字元型變數 grade,用來讓祥鏈分別存儲輸入的成績和計算出的等級。然後,使用 scanf 函數從鍵盤上讀入成績,使用除法運算將成績轉化為 1~10 的整數,並將其作為 switch 語句的選擇器。根據成績范圍,使用不同的 case 標號計算出等級,並將其賦值給 grade 變數。最後,使用 printf 函數輸出等級。
注意,在 switch 語句中,可以宴銀使用多個 case 標號來表示同一種情況,這樣可以簡化代碼。例如,case 10 和 case 9 都表示成績在 90 分以上的情況,因此可以將它們寫在一起。另外,在 switch 語句中,必須使用 break 語句來結束每個 case 分支,否則程序將繼續執行後面的 case 分支。
㈨ 一個Linux多進程編程
1 引言
對於沒有接觸過Unix/Linux操作系統的人來說,fork是最難理解的概念之一:它執行一次卻返回兩個值。fork函數是Unix系統最傑出的成就之一,它是七十年代UNIX早期的開發者經過長期在理論和實踐上的艱苦探索後取得的成果,一方面,它使操作系統在進程管理上付出了最小的代價,另一方面,又為程序員提供了一個簡潔明了的多進程方法。與DOS和早期的Windows不同,Unix/Linux系統是真正實現多任務操作的系統,可以說,不使用多進程編程,就不能算是真正的Linux環境下編程。
多線程程序設計的概念早在六十年代就被提出,但直到八十年代中期,Unix系統中才引入多線程機制,如今,由於自身的許多優點,多線程編程已經得到了廣泛的應用。
下面,我們將介紹在Linux下編寫多進程和多線程程序的一些初步知識。
2 多進程編程
什麼是一個進程?進程這個概念是針對系統而不是針對用戶的,對用戶來說,他面對的概念是程序。當用戶敲入命令執行一個程序的時候,對系統而言,它將啟動一個進程。但和程序不同的是,在這個進程中,系統可能需要再啟動一個或多個進程來完成獨立的多個任務。多進程編程的主要內容包括進程式控制制和進程間通信,在了解這些之前,我們先要簡單知道進程的結構。
2.1 Linux下進程的結構
Linux下一個進程在內存里有三部分的數據,就是"代碼段"、"堆棧段"和"數據段"。其實學過匯編語言的人一定知道,一般的CPU都有上述三種段寄存器,以方便操作系統的運行。這三個部分也是構成一個完整的執行序列的必要的部分。
"代碼段",顧名思義,就是存放了程序代碼的數據,假如機器中有數個進程運行相同的一個程序,那麼它們就可以使用相同的代碼段。"堆棧段"存放的就是子程序的返回地址、子程序的參數以及程序的局部變數。而數據段則存放程序的全局變數,常數以及動態數據分配的數據空間(比如用malloc之類的函數取得的空間)。這其中有許多細節問題,這里限於篇幅就不多介紹了。系統如果同時運行數個相同的程序,它們之間就不能使用同一個堆棧段和數據段。
2.2 Linux下的進程式控制制
在傳統的Unix環境下,有兩個基本的操作用於創建和修改進程:函數fork( )用來創建一個新的進程,該進程幾乎是當前進程的一個完全拷貝;函數族exec( )用來啟動另外的進程以取代當前運行的進程。Linux的進程式控制制和傳統的Unix進程式控制制基本一致,只在一些細節的地方有些區別,例如在Linux系統中調用vfork和fork完全相同,而在有些版本的Unix系統中,vfork調用有不同的功能。由於這些差別幾乎不影響我們大多數的編程,在這里我們不予考慮。
2.2.1 fork( )
fork在英文中是"分叉"的意思。為什麼取這個名字呢?因為一個進程在運行中,如果使用了fork,就產生了另一個進程,於是進程就"分叉"了,所以這個名字取得很形象。下面就看看如何具體使用fork,這段程序演示了使用fork的基本框架:
void main(){
int i;
if ( fork() == 0 ) {
/* 子進程程序 */
for ( i = 1; i <1000; i ++ ) printf("This is child process\n");
}
else {
/* 父進程程序*/
for ( i = 1; i <1000; i ++ ) printf("This is process process\n");
}
}
程序運行後,你就能看到屏幕上交替出現子進程與父進程各列印出的一千條信息了。如果程序還在運行中,你用ps命令就能看到系統中有兩個它在運行了。
那麼調用這個fork函數時發生了什麼呢?fork函數啟動一個新的進程,前面我們說過,這個進程幾乎是當前進程的一個拷貝:子進程和父進程使用相同的代碼段;子進程復制父進程的堆棧段和數據段。這樣,父進程的所有數據都可以留給子進程,但是,子進程一旦開始運行,雖然它繼承了父進程的一切數據,但實際上數據卻已經分開,相互之間不再有影響了,也就是說,它們之間不再共享任何數據了。它們再要交互信息時,只有通過進程間通信來實現,這將是我們下面的內容。既然它們如此相象,系統如何來區分它們呢?這是由函數的返回值來決定的。對於父進程,fork函數返回了子程序的進程號,而對於子程序,fork函數則返回零。在操作系統中,我們用ps函數就可以看到不同的進程號,對父進程而言,它的進程號是由比它更低層的系統調用賦予的,而對於子進程而言,它的進程號即是fork函數對父進程的返回值。在程序設計中,父進程和子進程都要調用函數fork()下面的代碼,而我們就是利用fork()函數對父子進程的不同返回值用if...else...語句來實現讓父子進程完成不同的功能,正如我們上面舉的例子一樣。我們看到,上面例子執行時兩條信息是交互無規則的列印出來的,這是父子進程獨立執行的結果,雖然我們的代碼似乎和串列的代碼沒有什麼區別。
讀者也許會問,如果一個大程序在運行中,它的數據段和堆棧都很大,一次fork就要復制一次,那麼fork的系統開銷不是很大嗎?其實UNIX自有其解決的辦法,大家知道,一般CPU都是以"頁"為單位來分配內存空間的,每一個頁都是實際物理內存的一個映像,象INTEL的CPU,其一頁在通常情況下是4086位元組大小,而無論是數據段還是堆棧段都是由許多"頁"構成的,fork函數復制這兩個段,只是"邏輯"上的,並非"物理"上的,也就是說,實際執行fork時,物理空間上兩個進程的數據段和堆棧段都還是共享著的,當有一個進程寫了某個數據時,這時兩個進程之間的數據才有了區別,系統就將有區別的"頁"從物理上也分開。系統在空間上的開銷就可以達到最小。
下面演示一個足以"搞死"Linux的小程序,其源代碼非常簡單:
void main()
{
for( ; ; ) fork();
}
這個程序什麼也不做,就是死循環地fork,其結果是程序不斷產生進程,而這些進程又不斷產生新的進程,很快,系統的進程就滿了,系統就被這么多不斷產生的進程"撐死了"。當然只要系統管理員預先給每個用戶設置可運行的最大進程數,這個惡意的程序就完成不了企圖了。
2.2.2 exec( )函數族
下面我們來看看一個進程如何來啟動另一個程序的執行。在Linux中要使用exec函數族。系統調用execve()對當前進程進行替換,替換者為一個指定的程序,其參數包括文件名(filename)、參數列表(argv)以及環境變數(envp)。exec函數族當然不止一個,但它們大致相同,在Linux中,它們分別是:execl,execlp,execle,execv,execve和execvp,下面我只以execlp為例,其它函數究竟與execlp有何區別,請通過manexec命令來了解它們的具體情況。
一個進程一旦調用exec類函數,它本身就"死亡"了,系統把代碼段替換成新的程序的代碼,廢棄原有的數據段和堆棧段,並為新程序分配新的數據段與堆棧段,唯一留下的,就是進程號,也就是說,對系統而言,還是同一個進程,不過已經是另一個程序了。(不過exec類函數中有的還允許繼承環境變數之類的信息。)
那麼如果我的程序想啟動另一程序的執行但自己仍想繼續運行的話,怎麼辦呢?那就是結合fork與exec的使用。下面一段代碼顯示如何啟動運行其它程序:
char command[256];
void main()
{
int rtn; /*子進程的返回數值*/
while(1) {
/* 從終端讀取要執行的命令 */
printf( ">" );
fgets( command, 256, stdin );
command[strlen(command)-1] = 0;
if ( fork() == 0 ) {
/* 子進程執行此命令 */
execlp( command, command );
/* 如果exec函數返回,表明沒有正常執行命令,列印錯誤信息*/
perror( command );
exit( errorno );
}
else {
/* 父進程, 等待子進程結束,並列印子進程的返回值 */
wait ( &rtn );
printf( " child process return %d\n",. rtn );
}
}
}
此程序從終端讀入命令並執行之,執行完成後,父進程繼續等待從終端讀入命令。熟悉DOS和WINDOWS系統調用的朋友一定知道DOS/WINDOWS也有exec類函數,其使用方法是類似的,但DOS/WINDOWS還有spawn類函數,因為DOS是單任務的系統,它只能將"父進程"駐留在機器內再執行"子進程",這就是spawn類的函數。WIN32已經是多任務的系統了,但還保留了spawn類函數,WIN32中實現spawn函數的方法同前述UNIX中的方法差不多,開設子進程後父進程等待子進程結束後才繼續運行。UNIX在其一開始就是多任務的系統,所以從核心角度上講不需要spawn類函數。
在這一節里,我們還要講講system()和popen()函數。system()函數先調用fork(),然後再調用exec()來執行用戶的登錄shell,通過它來查找可執行文件的命令並分析參數,最後它么使用wait()函數族之一來等待子進程的結束。函數popen()和函數system()相似,不同的是它調用pipe()函數創建一個管道,通過它來完成程序的標准輸入和標准輸出。這兩個函數是為那些不太勤快的程序員設計的,在效率和安全方面都有相當的缺陷,在可能的情況下,應該盡量避免。
2.3 Linux下的進程間通信
詳細的講述進程間通信在這里絕對是不可能的事情,而且筆者很難有信心說自己對這一部分內容的認識達到了什麼樣的地步,所以在這一節的開頭首先向大家推薦著名作者Richard Stevens的著名作品:《Advanced Programming in the UNIX Environment》,它的中文譯本《UNIX環境高級編程》已有機械工業出版社出版,原文精彩,譯文同樣地道,如果你的確對在Linux下編程有濃厚的興趣,那麼趕緊將這本書擺到你的書桌上或計算機旁邊來。說這么多實在是難抑心中的景仰之情,言歸正傳,在這一節里,我們將介紹進程間通信最最初步和最最簡單的一些知識和概念。
首先,進程間通信至少可以通過傳送打開文件來實現,不同的進程通過一個或多個文件來傳遞信息,事實上,在很多應用系統里,都使用了這種方法。但一般說來,進程間通信(IPC:InterProcess Communication)不包括這種似乎比較低級的通信方法。Unix系統中實現進程間通信的方法很多,而且不幸的是,極少方法能在所有的Unix系統中進行移植(唯一一種是半雙工的管道,這也是最原始的一種通信方式)。而Linux作為一種新興的操作系統,幾乎支持所有的Unix下常用的進程間通信方法:管道、消息隊列、共享內存、信號量、套介面等等。下面我們將逐一介紹。
2.3.1 管道
管道是進程間通信中最古老的方式,它包括無名管道和有名管道兩種,前者用於父進程和子進程間的通信,後者用於運行於同一台機器上的任意兩個進程間的通信。
無名管道由pipe()函數創建:
#include <unistd.h>
int pipe(int filedis[2]);
參數filedis返回兩個文件描述符:filedes[0]為讀而打開,filedes[1]為寫而打開。filedes[1]的輸出是filedes[0]的輸入。下面的例子示範了如何在父進程和子進程間實現通信。
#define INPUT 0
#define OUTPUT 1
void main() {
int file_descriptors[2];
/*定義子進程號 */
pid_t pid;
char buf[256];
int returned_count;
/*創建無名管道*/
pipe(file_descriptors);
/*創建子進程*/
if((pid = fork()) == -1) {
printf("Error in fork\n");
exit(1);
}
/*執行子進程*/
if(pid == 0) {
printf("in the spawned (child) process...\n");
/*子進程向父進程寫數據,關閉管道的讀端*/
close(file_descriptors[INPUT]);
write(file_descriptors[OUTPUT], "test data", strlen("test data"));
exit(0);
} else {
/*執行父進程*/
printf("in the spawning (parent) process...\n");
/*父進程從管道讀取子進程寫的數據,關閉管道的寫端*/
close(file_descriptors[OUTPUT]);
returned_count = read(file_descriptors[INPUT], buf, sizeof(buf));
printf("%d bytes of data received from spawned process: %s\n",
returned_count, buf);
}
}
在Linux系統下,有名管道可由兩種方式創建:命令行方式mknod系統調用和函數mkfifo。下面的兩種途徑都在當前目錄下生成了一個名為myfifo的有名管道:
方式一:mkfifo("myfifo","rw");
方式二:mknod myfifo p
生成了有名管道後,就可以使用一般的文件I/O函數如open、close、read、write等來對它進行操作。下面即是一個簡單的例子,假設我們已經創建了一個名為myfifo的有名管道。
/* 進程一:讀有名管道*/
#include <stdio.h>
#include <unistd.h>
void main() {
FILE * in_file;
int count = 1;
char buf[80];
in_file = fopen("mypipe", "r");
if (in_file == NULL) {
printf("Error in fdopen.\n");
exit(1);
}
while ((count = fread(buf, 1, 80, in_file)) > 0)
printf("received from pipe: %s\n", buf);
fclose(in_file);
}
/* 進程二:寫有名管道*/
#include <stdio.h>
#include <unistd.h>
void main() {
FILE * out_file;
int count = 1;
char buf[80];
out_file = fopen("mypipe", "w");
if (out_file == NULL) {
printf("Error opening pipe.");
exit(1);
}
sprintf(buf,"this is test data for the named pipe example\n");
fwrite(buf, 1, 80, out_file);
fclose(out_file);
}
2.3.2 消息隊列
消息隊列用於運行於同一台機器上的進程間通信,它和管道很相似,事實上,它是一種正逐漸被淘汰的通信方式,我們可以用流管道或者套介面的方式來取代它,所以,我們對此方式也不再解釋,也建議讀者忽略這種方式。
2.3.3 共享內存
共享內存是運行在同一台機器上的進程間通信最快的方式,因為數據不需要在不同的進程間復制。通常由一個進程創建一塊共享內存區,其餘進程對這塊內存區進行讀寫。得到共享內存有兩種方式:映射/dev/mem設備和內存映像文件。前一種方式不給系統帶來額外的開銷,但在現實中並不常用,因為它控制存取的將是實際的物理內存,在Linux系統下,這只有通過限制Linux系統存取的內存才可以做到,這當然不太實際。常用的方式是通過shmXXX函數族來實現利用共享內存進行存儲的。
首先要用的函數是shmget,它獲得一個共享存儲標識符。
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/shm.h>
int shmget(key_t key, int size, int flag);
這個函數有點類似大家熟悉的malloc函數,系統按照請求分配size大小的內存用作共享內存。Linux系統內核中每個IPC結構都有的一個非負整數的標識符,這樣對一個消息隊列發送消息時只要引用標識符就可以了。這個標識符是內核由IPC結構的關鍵字得到的,這個關鍵字,就是上面第一個函數的key。數據類型key_t是在頭文件sys/types.h中定義的,它是一個長整形的數據。在我們後面的章節中,還會碰到這個關鍵字。
當共享內存創建後,其餘進程可以調用shmat()將其連接到自身的地址空間中。
void *shmat(int shmid, void *addr, int flag);
shmid為shmget函數返回的共享存儲標識符,addr和flag參數決定了以什麼方式來確定連接的地址,函數的返回值即是該進程數據段所連接的實際地址,進程可以對此進程進行讀寫操作。
使用共享存儲來實現進程間通信的注意點是對數據存取的同步,必須確保當一個進程去讀取數據時,它所想要的數據已經寫好了。通常,信號量被要來實現對共享存儲數據存取的同步,另外,可以通過使用shmctl函數設置共享存儲內存的某些標志位如SHM_LOCK、SHM_UNLOCK等來實現。
㈩ 在linux下用c語言實現用多進程同步方法演示「生產者-消費者」問題
這個問題需要的知識主要包括:
1 多陪如進程間進行通信;
2 使用同步信號量(semaphore)和互斥信號量(mutex)進行數據保護。
參考代碼如下,可以參照注釋輔助理解:
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<pthread.h>
#include<semaphore.h>
#defineN2//消費者或者生產者的數目
#defineM10//緩沖數目
intin=0;//生產者放置產品的位置
intout=0;//消費者取產品的位置
intbuff[M]={0};//緩沖初始化為0,開始時沒有產品
sem_tempty_sem;//同步信號量,當滿了時阻止生產者放產品
sem_tfull_sem;//同步信號量,當沒產品時阻止消費者消費
pthread_mutex_tmutex;//互斥信號量,一次只有一個線程訪問緩沖
intproct_id=0;//生產者id
intprochase_id=0;//消費者id
/*列印緩沖情況*/
voidprint()
{
inti;
for(i=0;i<M;i++)
printf("%d",buff[i]);
printf(" ");
}
/*生產者方法*/
void*proct()
{
intid=++proct_id;
while(1)
{
//用sleep的數量可以調節生產和消費的速度,便於觀察
sleep(1);
//sleep(1);
sem_wait(&empty_sem);
pthread_mutex_lock(&mutex);
in=in%M;
printf("proct%din%d.like: ",id,in);
buff[in]=1;蘆罩啟
print();
++in;
pthread_mutex_unlock(&mutex);
sem_post(&full_sem);
}
}
/*消費者方法*/
void*prochase()
{
intid=++prochase_id;
while(1)
{
//用sleep的數量可以調節生產和消費的速度,便於觀察
sleep(1);
//sleep(1);
sem_wait(&full_sem);
pthread_mutex_lock(&mutex);
out=out%M;
printf("prochase%din%d.like: ",id,out);
buff[out]=0;
print();
++out;
pthread_mutex_unlock(&mutex);
sem_post(&empty_sem);
}
}
intmain()
{
pthread_tid1[N];
pthread_tid2[N];
inti;
intret[N];
//初始化同步信號量
intini1=sem_init(&empty_sem,0,M);
intini2=sem_init(&full_sem,0,0);
if(ini1&&ini2!=0)
{
printf("seminitfailed ");
exit(1);
}
//初始化互斥信號量
intini3=pthread_mutex_init(&mutex,NULL);
if(ini3!=0)
{
printf("mutexinitfailed ");
exit(1);
}
//創建N個生產者線程
for(i=0;i<N;i++)
{
ret[i]=pthread_create(&id1[i],NULL,proct,(void*)(&i));
if(ret[i]!=0)
{
printf("proct%dcreationfailed ",i);
exit(1);
}
}
//創建N個消費者線程
for(i=0;i<N;i++)
{
ret[i]=pthread_create(&id2[i],NULL,prochase,NULL);
if(ret[i]!=0)
{
printf("prochase%dcreationfailed ",i);
exit(1);
}
}
//銷毀線程
for(i=0;i<N;i++)
{
pthread_join(id1[i],NULL);
pthread_join(id2[i],NULL);
}
exit(0);
}
在Linux下編悶氏譯的時候,要在編譯命令中加入選項-lpthread以包含多線程支持。比如存儲的C文件為demo.c,要生成的可執行文件為demo。可以使用命令:
gcc demo.c -o demo -lpthread
程序中為便於觀察,使用了sleep(1);來暫停運行,所以查看輸出的時候可以看到,輸出是每秒列印一次的。