本學期操作系統課期中考覈需要寫兩篇小論文,前篇已經貼過了。今晚無聊,貼出此文,聊以慰籍空虛的心。要下載的朋友,請點擊這裏。
要求如下:
下面是我的論文,由於格式原因,敘述部分直接上圖了:
三、初遇signal
在實驗三《Linux進程間通信》---“消息機制的示例程序”中有如下源碼:
#include <sys/types.h>
…
…
…
void sigend(int);
…
…
…
int msgid;
int main(void)
{
struct mymsg msgbuf;
if((msgid=msgget(MY_KEY, IPC_CREAT|IPC_EXCL|0666)) < 0 )
{ /* message queue exists, act as client */
…
…
…
}
else /* acts as server */
{
signal(SIGINT, sigend);
signal(SIGTERM, sigend);
…
…
…
}
}
}
void sigend(int sig)
{
msgctl(msgid, IPC_RMID, 0);
exit(0);
}
四、Signal通信簡介
1、基本知識
軟中斷信號(signal,又簡稱爲信號)用來通知進程發生了異步事件。進程之間可以互相通過系統調用kill發送軟中斷信號。內核也可以因爲內部事件而給進程發送信號,通知進程發生了某個事件。收到信號的進程對各種信號有不同的處理方法。處理方法可以分爲三類:第一種是類似中斷的處理程序,對於需要處理的信號,進程可以指定處理函數,由該函數來處理。第二種方法是忽略某個信號,對該信號不做任何處理,就象未發生過一樣。第三種方法是對該信號的處理保留系統的默認值。這種缺省操作,對大部分的信號的缺省操作是使得進程終止。進程通過系統調用signal來指定進程對某個信號的處理行爲。
表頭文件: #include<signal.h>
功 能: 設置某一信號的對應動作
函數原型 : void (*signal(intsignum,void(* handler)(int)))(int);
或者:typedef void(*sig_t) ( int );
sig_t signal(int signum,sig_t handler);
參數說明:
第一個參數signum指明瞭所要處理的信號類型,它可以取除了SIGKILL和SIGSTOP外的任何一種信號。
第二個參數handler描述了與信號關聯的動作,它可以取以下三種值:
(1)一個無返回值的函數地址
此函數必須在signal()被調用前申明,handler中爲這個函數的名字。當接收到一個類型爲sig的信號時,就執行handler 所指定的函數。這個函數應有如下形式的定義:
void func(int sig);
sig是傳遞給它的唯一參數。執行了signal()調用後,進程只要接收到類型爲sig的信號,不管其正在執行程序的哪一部分,就立即執行func()函數。當func()函數執行結束後,控制權返回進程被中斷的那一點繼續執行。
(2)SIGIGN
這個符號表示忽略該信號,執行了相應的signal()調用後,進程會忽略類型爲sig的信號。
(3)SIGDFL
這個符號表示恢復系統對信號的默認處理。
函數說明 :
signal()會依參數signum指定的信號編號來設置該信號的處理函數。當指定的信號到達時就會跳轉到參數handler指定的函數執行。當一個信號的信號處理函數執行時,
如果進程又接收到了該信號,該信號會自動被儲存而不會中斷信號處理函數的執行,直到信號處理函數執行完畢再重新調用相應的處理函數。但是如果在信號處理函數執行時進程收到了其它類型的信號,該函數的執行就會被中斷。
返回值: 返回先前的信號處理函數指針,如果有錯誤則返回SIG_ERR(-1)。
附加說明 :在信號發生跳轉到自定的handler處理函數執行後,系統會自動將此處理函數換回原來系統預設的處理方式,如果要改變此操作請改用sigaction()。
下面的情況可以產生Signal:
1. 按下CTRL+C產生SIGINT
2. 硬件中斷,如除0,非法內存訪問(SIGSEV)等等
3. Kill函數可以對進程發送Signal
4. Kill命令。實際上是對Kill函數的一個包裝
2.Signals
各種信號的基本信息如下;
Signal |
Description |
SIGABRT |
由調用abort函數產生,進程非正常退出 |
SIGALRM |
用alarm函數設置的timer超時或setitimer函數設置的interval timer超時 |
SIGBUS |
某種特定的硬件異常,通常由內存訪問引起 |
SIGCANCEL |
由Solaris Thread Library內部使用,通常不會使用 |
SIGCHLD |
進程Terminate或Stop的時候,SIGCHLD會發送給它的父進程。缺省情況下該Signal會被忽略 |
SIGCONT |
當被stop的進程恢復運行的時候,自動發送 |
SIGEMT |
和實現相關的硬件異常 |
SIGFPE |
數學相關的異常,如被0除,浮點溢出,等等 |
SIGFREEZE |
Solaris專用,Hiberate或者Suspended時候發送 |
SIGHUP |
發送給具有Terminal的Controlling Process,當terminal被disconnect時候發送 |
SIGILL |
非法指令異常 |
SIGINFO |
BSD signal。由Status Key產生,通常是CTRL+T。發送給所有Foreground Group的進程 |
SIGINT |
由Interrupt Key產生,通常是CTRL+C或者DELETE。發送給所有ForeGround Group的進程 |
SIGIO |
異步IO事件 |
SIGIOT |
實現相關的硬件異常,一般對應SIGABRT |
SIGKILL |
無法處理和忽略。中止某個進程 |
SIGLWP |
由Solaris Thread Libray內部使用 |
SIGPIPE |
在reader中止之後寫Pipe的時候發送 |
SIGPOLL |
當某個事件發送給Pollable Device的時候發送 |
SIGPROF |
Setitimer指定的Profiling Interval Timer所產生 |
SIGPWR |
和系統相關。和UPS相關。 |
SIGQUIT |
輸入Quit Key的時候(CTRL+\)發送給所有Foreground Group的進程 |
SIGSEGV |
非法內存訪問 |
SIGSTKFLT |
Linux專用,數學協處理器的棧異常 |
SIGSTOP |
中止進程。無法處理和忽略。 |
SIGSYS |
非法系統調用 |
SIGTERM |
請求中止進程,kill命令缺省發送 |
SIGTHAW |
Solaris專用,從Suspend恢復時候發送 |
SIGTRAP |
實現相關的硬件異常。一般是調試異常 |
SIGTSTP |
Suspend Key,一般是Ctrl+Z。發送給所有Foreground Group的進程 |
SIGTTIN |
當Background Group的進程嘗試讀取Terminal的時候發送 |
SIGTTOU |
當Background Group的進程嘗試寫Terminal的時候發送 |
SIGURG |
當out-of-band data接收的時候可能發送 |
SIGUSR1 |
用戶自定義signal 1 |
SIGUSR2 |
用戶自定義signal 2 |
SIGVTALRM |
setitimer函數設置的Virtual Interval Timer超時的時候 |
SIGWAITING |
Solaris Thread Library內部實現專用 |
SIGWINCH |
當Terminal的窗口大小改變的時候,發送給Foreground Group的所有進程 |
SIGXCPU |
當CPU時間限制超時的時候 |
SIGXFSZ |
進程超過文件大小限制
SIGXRES
Solaris專用,進程超過資源限制的時候發送
五、司機售票員問題
問題描述:
創建driver進程代表司機,seller進程代表售票員。
售票員捕捉SIGINT(代表開車),發SIGUSR1給司機,司機打印("let's go")。
售票員捕捉SIGQUIT(代表停車),發SIGUSR2給司機,司機打印("stop the bus")
司機捕捉SIGTSTP(代表車到總站),發SIGUSR1給售票員,售票員打印("please get off thebus")
Driver.c源代碼如下:
/* Driver.c: Act as driver
*author : houjialin
*To compile: gcc Driver.c –o driver
*/
#include<unistd.h>
#include<signal.h>
#include<stdlib.h>
#include<error.h>
#include<stdio.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#define SHM_Key 3003 //共享存儲區Key
int shmid;//共享存儲區ID
int *shmptr;
int Sellerpid;
void CreatSHM()//創建共享存儲區,將自己的進程ID放入其中
{
if((shmid=shmget(SHM_Key, sizeof(int), IPC_CREAT|0666)) < 0)
printf("shmget error"),exit(1);
if((shmptr=(int *)shmat(shmid, 0, 0)) == (int *)-1)
printf("shmat error"),exit(1);
*shmptr=getpid();
}
void driversigusr1(int signo)
{
printf("let's go\n");
}
void driversigusr2(int signo)
{
printf("stop the bus\n");
}
void driversigtstp(int signo)
{
kill(Sellerpid,SIGUSR1);
}
int main() /*act as Driver*/
{
CreatSHM();
while(*shmptr==getpid());//等待Seller將自己pid放入SHM
Sellerpid=*shmptr;
signal(SIGUSR1,driversigusr1);//Driver對信號SIGUSR1響應函數driversigusr1
signal(SIGUSR2,driversigusr2);//Driver對信號SIGUSR2響應函數driversigusr2
signal(SIGTSTP,driversigtstp);//Driver對信號SIGSTSP(Ctrl + z)響應函數driversigtstp
while(1);
}
Seller.c源代碼如下:
/* Seller.c: Act as Seller
*author : houjialin
*To compile: gcc Seller.c –o seller
*/
#include<unistd.h>
#include<signal.h>
#include<stdlib.h>
#include<error.h>
#include<stdio.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#define SHM_Key 3003 //共享存儲區Key
int shmid;
int *shmptr;
int Driverpid;//司機進程ID
void getID()//通過共享存儲區獲取Driverpid,並將自己的放入其中
{
shmid=shmget(SHM_Key, sizeof(int), 0666);
shmptr=(int *)shmat(shmid, 0, 0);
Driverpid=*shmptr;
*shmptr=getpid();
}
void sellersigint(int signo)
{
kill(Driverpid,SIGUSR1);
}
void sellersigquit(int signo)
{
kill(Driverpid,SIGUSR2);
}
void sellersigusr1(int signo)
{
printf("\nplease get off the bus\n");
}
int main()//act as Seller
{
getID();
signal(SIGINT, sellersigint);//Seller對信號SIGINT(Ctrl + c)響應函數sellersigint
signal(SIGQUIT,sellersigquit);//Seller對信號SIGQUIT(Ctrl + \)響應函數sellersigquit
signal(SIGUSR1,sellersigusr1); //Seller對信號SIGUSR1響應函數sellersigusr1
while(1);
}
運行結果如下:
Driver.c |
Selller.c |
houjialin@houjialin-ThinkPad-Edge:~/Documents$ ./driver
let's go stop the bus ^Z |
houjialin@houjialin-ThinkPad-Edge:~/Documents/期中$ ./seller ^C^\ please get off the bus
|
結果分析:
當在seller進程中按下Ctrl+c(Ctrl+\,Ctrl+z同理)時,seller通過Signal捕獲該信號,然後調用sellersigint函數處理該信號。Sellersigint函數通過系統調用kill發送SIGUSR1給driver進程(通過進程ID標識)。在driver進程中,當捕獲到SIGUSR1後,掉用函數driversigusr1打印let’s go.
六、總結
到此,通過Signal刪除消息隊列的原理已經基本明白了。當按下Ctrl+C結束該進程時(Kill同理),當前進程通過Signal捕獲該信號然後調用sigend(int) 處理。在sigend(int) 中,通過msgctl(msgid, IPC_RMID, 0)刪除相應的消息隊列。
以上只是對Signal通信的一些簡單的介紹。在Linux操作系統中,許多系統進程通過Signal進行通信此處尚未涉及,有待今後研究。
附錄一:
msgServe.c源代碼:
/* msgServe.c: Act as Serve
*author : houjialin
*To compile: gcc msgServe.c –o msgServe
*/
#include<stdio.h>
#include<sys/types.h>
#include<sys/ipc.h>
#include<sys/msg.h>
#define Histype 4
#define Mytype 3
#define MY_KEY 20
struct mymessage
{
long mtype;
char buffer[200];
} message;
int msgid;
void server()
{
msgid=msgget(MY_KEY, IPC_CREAT|IPC_EXCL|0666);
if(msgid<0)
{
printf("消息隊列已經存在!");
exit(0);
}
printf("server start:\n");
while(1)
{
msgrcv(msgid,&message,sizeof(struct mymessage),Histype,0);
printf("Question is:\n%s", message.buffer);
printf("Here is the answer :\n");
fgets(message.buffer,sizeof(struct mymessage)-sizeof(long)-1,stdin);
message.mtype=Mytype;
msgsnd(msgid, &message, sizeof(struct mymessage), 0);
}
}
int main()
{
server();
}
msgClient源代碼:
/* msgClient.c: Act as Client
*author : houjialin
*To compile: gcc msgClient.c –o msgClient
*/
#include<stdio.h>
#include<sys/types.h>
#include<sys/ipc.h>
#include<sys/msg.h>
#define Mytype 4
#define Histype 3
#define MY_KEY 20
struct mymessage
{
long mtype;
char buffer[200];
} message;
int msgid;
void client()
{
msgid=msgget(MY_KEY, 0666);
if(msgid>=0)
printf("welcome to numeber %d queue!Input your questions\n",msgid);
else
{
exit(0);
}
fgets(message.buffer, sizeof(struct mymessage)-sizeof(long)-1, stdin);
while(1)
{
message.mtype=Mytype;
msgsnd(msgid, &message, sizeof(struct mymessage), 0);
msgrcv(msgid,&message,sizeof(struct mymessage),Histype,0);
printf("The answer is: %sInput your question:\n",message.buffer);
fgets(message.buffer,sizeof(struct mymessage)-sizeof(long)-1,stdin);
}
}
int main()
{
client();
}