1. 程式人生 > >作業系統課程設計(三):Linux程序管理

作業系統課程設計(三):Linux程序管理

一、設計內容

  1. 實現一個模擬shell:編寫三個不同的程式:cmd1.c, cmd2.c, cmd3.c,每個程式輸出一句話,分別編譯成可執行檔案cmd1, cmd2, cmd3。然後再編寫一個程式,模擬shell程式的功能,能根據使用者輸入的字串(表示相應的命令名),去為相應的命令建立子程序並讓它去執行相應的程式,而父程序則等待子程序的結束,然後再等待接收下一條命令。如果接收到的命令為exit,則父程序結束,如果接收到無效命令,則顯示”command not found”,繼續等待。

  2. 實現一個管道通訊程式:由父程序建立一個管道,然後再建立3個子程序,並由這三個子程序用管道與父程序之間進行通訊:子程序傳送資訊,父程序等三個子程序全部發完訊息後再接收資訊。通訊的具體內容可根據自己的需要隨意設計,要求能夠實驗阻塞型讀寫過程的各種情況,並要求實現程序間對管道的互斥訪問。執行程式,觀察各種情況下,程序實際讀寫的位元組數以及程序阻塞喚醒情況。

  3. 利用linux的訊息佇列通訊機制實現兩個執行緒間的通訊:編寫程式建立兩個執行緒:sender執行緒和receive執行緒,其中sender執行函式sender(),他建立一個訊息佇列,然後,迴圈等待使用者通過終端輸入一串字元,將這串字元通過訊息佇列傳送給receiver執行緒,直到使用者輸入”exit”為止;最後,它向receiver執行緒傳送訊息”end”,並且等待receiver的應答,直到應答訊息後,將接收到的應答訊息顯示在終端上,刪除相關訊息佇列,結束程式執行。receiver執行緒執行receive(),它通過訊息佇列接收來自sender的訊息,將訊息顯示在終端螢幕,直到接收到”end”的訊息後它向sender傳送一個應答訊息”over”,結束程式執行。使用無名訊號量實現兩個執行緒之間的同步與互斥。

  4. 利用linux的共享記憶體通訊機制實現兩個程序間的通訊:編寫程式sender,它建立一個共享記憶體,然後等待使用者通過終端輸入一串字元,並將這串字元通過共享記憶體傳送給receiver,最後,等待receiver應答,等到應答訊息後,它接收到的應答訊息顯示在終端螢幕上,刪除共享記憶體,結束程式執行。編寫receiver程式,它通過共享記憶體接收來自sender的訊息,將訊息顯示在終端螢幕上,然後再通過該共享記憶體向sender傳送一個應答訊息”over”,結束程式的執行。使用有名訊號量或System V訊號量實現兩個程序對共享記憶體的互斥使用。

二、程式碼實現

(1)實現一個模擬shell

1-1 編寫cmd1.c cmd2.c cmd3.c,可自己設計

//cmd1.c
#include<stdio.h>
int main()
{
        printf("this is the cmd1111111\n");
        return 0;
}

//cmd2.c
#include<stdio.h>
int main()
{
        printf("this is the cmd222222\n");
        return 0;
}

//cmd3.c
#include<stdio.h>
int main()
{
        printf("this is the cmd333333\n");
        return 0;
}

1-2 編寫Makefile(clear下面的三個rm的前面是tab,不是單純空行)

all: myshell cmd1 cmd2 cmd3

.PHONY : clean

myshell.o : myshell.c

clean : 
        rm cmd1 cmd2 cmd3
        rm myshell
        rm *.o

1-3 編寫myshell.c

#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/wait.h>

#define CMD_COLLECTION_LEN 4 //命令陣列的長度(有哪幾個命令)

//command index
#define INVALID_COMMAND -1 //無效命令返回-1
#define EXIT    0
#define CMD_1 1
#define CMD_2 2
#define CMD_3 3

//bool
#define TRUE 1

char *cmdStr [CMD_COLLECTION_LEN ]= {"exit","cmd1","cmd2","cmd3"};


//對比所有命令引數,如果有一樣的,就返回對應數字,用於後面執行
int getCmdIndex(char *cmd)
{
    int i;

    for(i=0;i<CMD_COLLECTION_LEN;i++)
    {
        if (strcmp(cmd,cmdStr[i])==0)
        {
            return i;
        }
    }

    return -1;
}


/*
建立子程序,這裡使用了execl,後面的l表示list,即引數列表。
第一引數為path(要執行的檔案路徑),最後一個引數必須是NULL,
中間的為要傳送的引數
*/
void myFork(int cmdIndex)
{
    pid_t pid;

    if((pid = fork())<0)
    {
        printf("建立子程序錯誤\n");
        exit(0);
    }
    else if (pid == 0)
    {
        int execl_status = -1;

        printf("子程序正在執行\n");

        switch(cmdIndex)
        {
            case CMD_1:
                execl_status = execl("./cmd1","cmd1",NULL);
                break;
            case CMD_2:
                execl_status = execl("./cmd2","cmd2",NULL);
                break;
            case CMD_3:
                execl_status = execl("./cmd3","cmd3",NULL);
                break;
            default:
                printf("無此命令!!!\n");
                break;
        }

        if(execl_status<0)
        {
            printf("建立錯誤\n");
            exit(0);
        }

        printf("執行完畢!\n");
        exit(0);
    }
    else{
        return;
    }

}

//執行cmd
void runCMD(int cmdIndex)
{
    switch(cmdIndex)
    {
        case INVALID_COMMAND:
            printf("Command Not Found \n"); //沒有找到該命令
            break;
        case EXIT: //exit命令返回0
            exit(0);
            break;
        default:
            myFork(cmdIndex); //建立子程序執行
            break;
    }
}

int main()
{
    pid_t pid;

    char cmdStr[30]; //命令陣列(最長30)

    int cmdIndex; //用於顯示執行哪個資料
    while(TRUE)
    {
        printf("\n輸入命令\n>>:");
        scanf("%s",cmdStr);
        cmdIndex = getCmdIndex(cmdStr);
        runCMD(cmdIndex); //根據數字執行不同的cmd
        wait(0);
    }
}

1-4 編譯:

make
  • 1

1-5 執行截圖示例: ![在這裡插入圖片描述](https://img-blog.csdn.net/20181022103145242?watermark/2/text/aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L1ZpY1RyZWU=/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70)

(2)實現一個管道通訊程式

2-1 編寫pipe_communication.c檔案

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/wait.h>
#include <string.h>

#define READ 0 //filedes[0]用於讀

#define WRITE 1 //filedes[1]用於寫

int main() {

    /*
    函式原型:pipe(int filedes[2])
    引數含義:filedes[0]對應管道讀端,filedes[1]對應管道寫端
    功能:pipe在記憶體緩衝區中建立一個管道,並將讀寫該管道的一對檔案描述符儲存在filedes所指陣列中
    返回值:成功返回0,失敗返回-1
    */
    int filedes[2];

    pid_t pid1,pid2,pid3;//pid_t本質就是int

    char buf[256]; //用作read的緩衝區,儲存讀取的字元

    pipe(filedes);  //建立無名管道

    if((pid1 = fork()) == -1) { //建立子程序
        printf("fork error(pid1)!\n");
        exit(1);
    }
    if(pid1 == 0) {
        sleep(1); //掛起一秒
        printf("正在產生子程序pid1:%d\n",getpid());
        //子程序向父程序寫資料,關閉管道的讀端
        close(filedes[READ]);
        write(filedes[WRITE], "pid111111\n", strlen("pid111111\n"));
        exit(0);
    }



    if ((pid2 = fork()) == -1) {
        printf("fork error(pid2)\n");
        exit(1);
    }
    if (pid2 == 0) {
        sleep(1);
        printf("正在產生子程序pid2:%d\n",getpid());
        close(filedes[READ]);
        write(filedes[WRITE], "pid222222\n", strlen("pid222222\n"));
        exit(0);
    }


    if ((pid3 = fork()) == -1) {
        printf("fork error(pid3)\n");
        exit(1);
    }
    if (pid3 == 0) {
        sleep(1);
        printf("正在產生子程序pid3:%d\n",getpid());

        close(filedes[READ]);
        write(filedes[WRITE], "pid333333\n", strlen("pid333333\n"));

        exit(0);
    }
    else {
        //waitpid()會暫時停止目前程序的執行,直到有訊號來或者子程序結束
        pid1 = waitpid(pid1, NULL, WUNTRACED);
        pid2 = waitpid(pid2, NULL, WUNTRACED);
        pid3 = waitpid(pid3, NULL, WUNTRACED);
        printf("main pid: %d\n",getpid());
        printf("wait pid: %d %d %d 返回資訊\n",pid1,pid2,pid3);
        /*父程序從管道讀取子程序寫的資料,關閉管道的寫端*/
        close(filedes[WRITE]);
        //read():讀取的資料儲存在緩衝區buf
        read(filedes[READ], buf, sizeof(buf));
        printf("3個子程序傳輸的資料為:\n%s\n", buf);
    }
    return 0;
}

2-2 Makefile

pipe_communication : pipe_communication.o
.PHONY : clean
clean: 
        rm *.o
        rm pipe_communication

2-3 make編譯

make

2-4 執行 這裡寫圖片描述

(3)利用linux的訊息佇列通訊機制實現兩個執行緒間的通訊

3.1 程式碼(message_queue.c)

#include <pthread.h>
#include <semaphore.h>
#include <sys/types.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/stat.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#include <errno.h>

#define TRUE 1

#define BUF_SIZE 255 //緩衝大小
//S_IRUSR|S_IWUSR:允許檔案建立者讀取|寫入(感覺就是賦予許可權)
#define PERM S_IRUSR|S_IWUSR
#define KEY_NUM 1000


typedef struct msgbuf msgbuf;

//訊息緩衝區結構體
struct msgbuf
{
    long mtype; //訊息型別
    char mtext[BUF_SIZE + 1]; //256,訊息正文
};


//sem_t 訊號量的資料型別,本質是個長整型的數
sem_t full;
sem_t empty;
sem_t mutex;

//pthread_t 宣告執行緒,類似於pid_t
pthread_t write_pid; 
pthread_t read_pid;

/*
IPC物件鍵值,每個IPC物件都關聯著一個唯一的長整型的鍵值,
不同程序通過相同相同的鍵值可訪問到同一個IPC物件。
若為0,建立一個新的訊息佇列,若大於0(通常通過ftok()生成的)
*/
key_t key;

//messageid
int msgid;

struct msgbuf msg;

//初始化
void Init()
{
    /*
    函式:sem_init(sem_t *sem,int pshared,unsigned int value)
    引數:sem表示一個訊號量,pashared(0-訊號量被程序內的執行緒共享,非0-程序之間共享),value訊號量初始值
    */
    sem_init(&full,0,0);
    sem_init(&empty,0,1);
    sem_init(&mutex,0,1);

    key = KEY_NUM;//給鍵值賦值

    //建立訊息佇列
    /*
    函式:msgget(key_t key,int smgflag)。新教材p107
    引數:key(訊息佇列鍵值,具體看上面)。msgflg(對訊息佇列的訪問許可權和控制命令的組合)
    功能:如果引數msgflag為IPC_CREATE,則semget()新建立一個訊息佇列並返回其識別符號,
          或返回具有相同鍵值的已存在的訊息佇列的識別符號
    返回值:成功返回訊息佇列的識別符號,失敗返回-1
    */
    if((msgid = msgget(key,PERM|IPC_CREAT)) == -1)
    {
        fprintf(stderr, "Create Message Queue Error %s\n",strerror(errno) );
        exit(EXIT_FAILURE);
    }   
}

//讀取資訊
void * ReadProcess(void *arg)
{
    msgbuf msg;
    //init msg
    msg.mtype = 1;
    while(TRUE)
    {
        //sem_wait阻塞程序,直到訊號量>=0,解除阻塞後sem值-1,表示公共資源使用後減少
        sem_wait(&full);
        sem_wait(&mutex);

        //從訊息佇列獲取內容
        /*
        函式:ssize_t msgrcv(int msqid,struct msgbuf *msgp,size_t msgsz,long msgtyp,int msgflg)
        引數:msqid(訊息佇列的識別符號),
              msgp(用來存放接受到的訊息內容的緩衝區指標)
              msgsz(訊息正文的長度),
              msgtyp(接收的訊息型別,0-接受訊息佇列中第一個訊息,>0接收第一個型別為msgtyp的訊息,<0接收第一個型別小於等於msgtyp的絕對值的訊息)
              msgflg(0-沒有可接收的訊息時,呼叫程序阻塞。其他略)
        返回值:接收成功,返回實際接收到的訊息正文的位元組數,否則返回-1
        */
        msgrcv(msgid,&msg,sizeof(msgbuf),1,0);//接收型別為1的訊息(即mtype=1)

        //如果接受到"end"
        if(strcmp(msg.mtext,"end") == 0)
        {
            msg.mtype = 2;
            strncpy(msg.mtext,"over",BUF_SIZE);
            //msgsnd用於向識別符號為msqid的訊息佇列傳送一個訊息(即傳送over)
            msgsnd(msgid,&msg,sizeof(msgbuf),0);
            sem_post(&empty);
        sem_post(&mutex);
            break;
        }
        //print message
        printf("Receive:  %s\n\n",msg.mtext);

        //sem_post增加訊號量的值,當有執行緒阻塞在這個訊號量時,該函式會使其中一個執行緒不在阻塞
        sem_post(&empty);
        sem_post(&mutex);
    }
    exit(EXIT_SUCCESS);
}

void * WriteProcess(void *arg)
{   
    char input[50];
    msgbuf msg;
    msg.mtype = 1;
    while (TRUE)
    {
        sem_wait(&empty);
        sem_wait(&mutex);
        sleep(0.1);
        printf("Sent: Please Input the message you want to send.\n");
        scanf("%s",input);
        if(strcmp(input,"exit") == 0)
        {
            strncpy(msg.mtext,"end",BUF_SIZE);
            msgsnd(msgid,&msg,sizeof(msgbuf),0);
            sem_post(&full);
            sem_post(&mutex);
            break; //輸出exit後,轉化為end,然後跳出while迴圈
        }
        strncpy(msg.mtext,input,BUF_SIZE);
        msgsnd(msgid,&msg,sizeof(msgbuf),0);
        printf("Sent: %s\n",msg.mtext );
        //semaphore
        sem_post(&full);
        sem_post(&mutex);
    }



    // Clear Node
    memset(&msg,'\0',sizeof(msgbuf));
    // Block ,waiting for msg with type = 2
    msgrcv(msgid,&msg,sizeof(msgbuf),2,0);
    printf("Sent:%s\n",msg.mtext );

    //Remove Message Queue
    if( msgctl (msgid,IPC_RMID,0) == -1)
    {
        fprintf(stderr, "Remove Message Queue Error%s\n", strerror(errno));
        exit(EXIT_FAILURE);
    }
    exit(EXIT_SUCCESS);
}

int main()
{
    Init();
    pthread_create(&write_pid,NULL,WriteProcess,NULL);
    pthread_create(&read_pid,NULL,ReadProcess,NULL);
    //waiting for the thread end
    pthread_join(write_pid,NULL);
    pthread_join(read_pid,NULL);
    printf("Main Function End...\n");
    return 0;
}

3-2 Makefile

message_queue : message_queue.o
    cc -pthread -o message_queue message_queue.o

.PHONY : clean


clean:
    rm message_queue
    rm *.o


3-3 執行 make 3-4 截圖 這裡寫圖片描述

(4)利用linux的共享記憶體通訊機制實現兩個程序間的通訊

4-1 Makefile

all : init sender receiver
.PHONY : clean
init : init.o common.o
    cc -pthread -o init init.o common.o
sender : sender.o common.o
    cc -pthread -o sender sender.o common.o
receiver : receiver.o common.o
    cc -pthread -o receiver receiver.o common.o
init.o : common.h           
sender.o : common.h
receiver.o : common.h
clean : 
    rm  init 
    rm  receiver
    rm  sender 
    rm  *.o

4-2 common.h(定義一些用到的標頭檔案)

#ifndef   _COMMON_H_
#define   _COMMON_H_
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <pthread.h>  
#include <semaphore.h>
#include <sys/types.h>
#include <unistd.h>
#include <sys/ipc.h>
#include <sys/shm.h>

static const char * MUTEX_NAME = "mutex_shm";
static const char * FULL_NAME  = "full_shm";
//static const char * PATH_NAME = "tmp/shmtemp";

//constant define
#define SHM_SIZE 1024 //輸入的最大長度

#define KEY_NUM 1000

//返回共享記憶體的識別符號
int GetShmId(key_t key);
void SemInit();
void SemDestroy();
void  P(sem_t *sem);
void  V(sem_t *sem);
#endif

4-3 common.c 一些公用的函式,如初始訊號量等

#include "common.h"

/*
key_t GetKey(const char * pathname)
{
    //int fd = open(pathname,O_CREAT,0666);
    int fd = open("log.txt",O_RDWR|O_CREAT,S_IRWXU);
    if(fd < 0)
    {
        perror("open file error");
        return -1;
    }

    close(fd);

    return ftok(pathname,0);
}

*/

int GetShmId(key_t key)
{
    int shmid;

    shmid = shmget(key,SHM_SIZE,IPC_CREAT|0666);
    if(shmid < 0)
    {
        perror("Receiver: Shmget Error");
        exit(EXIT_FAILURE);
    }

    return shmid;
}

/*
* create mutex + semaphore
* init those value
*/
void SemInit()
{
     /*
     * Funtion Prototype: 
     *
     *  sem_t *sem_open(const char *name, int oflag,
     *                  mode_t mode, unsigned int value);
     *                 
     * name     : MUTEX_NAME    "mutex_shm"
     * oflag    : O_CREAT       Create and initialize it if not exist
     * mode_t   : file perssion -rw-r--r--
     * value    : 1
     */
     if((sem_open(MUTEX_NAME,O_CREAT,0644,1)) < 0)
     {
        perror("sem_open");
        exit(EXIT_FAILURE);
     }

     if((sem_open(FULL_NAME,O_CREAT,0644,0)) < 0){
        perror("sem_open");
        exit(EXIT_FAILURE);
     }
}


/*
* close and unlink semaphore that we crated
*/
void SemDestroy()
{
    sem_t * mutexPtr = sem_open(MUTEX_NAME,O_CREAT); 
    sem_t * fullPtr= sem_open(FULL_NAME,O_CREAT);

    /* Destroy mutex */
    sem_close(mutexPtr);                // int sem_close(sem_t *sem);
    sem_unlink(MUTEX_NAME);         // int sem_unlink(const char *name);

    /* Destory full*/
    sem_close(fullPtr);
    sem_unlink(FULL_NAME);
}


void  P(sem_t *semPtr)
{
    sem_wait(semPtr);                   //int sem_wait(sem_t *sem);
}

void  V(sem_t *semPtr)
{
    sem_post(semPtr);                   //int sem_post(sem_t *sem);
}

4-4 sender.c (傳送訊息到記憶體區域)

#include "common.h"


//key
key_t key;

//shared memory
int shmid;
char * shmptr;
char input[SHM_SIZE];

//semaphore 
sem_t * full;
sem_t * mutex;
                            //semaphore


void Init()
{
    key = KEY_NUM;                  //init key
    shmid  = GetShmId(key);         // init shared memory
    shmptr = shmat(shmid,NULL,0);       // attach segement to vitural ...?
    //semaphore init
    full = sem_open(FULL_NAME,O_CREAT);
    mutex = sem_open(MUTEX_NAME,O_CREAT);
}

void SaveMessage()
{

    P(mutex);                       
    strcpy(shmptr,input);
    V(mutex);

    V(full);
}

int main(int argc, char const *argv[])
{


    Init();

    /*waiting for user to input message*/
    scanf("%s",input);                  //input message from shell 
                                        // TODO input a whole line
    SaveMessage();

    printf("Sender:  Process End\n");
    return 0;
}

4-5 receiver.c(從記憶體獲取訊息)

#include "common.h"


//key
key_t key;

//shared memory
int shmid;
char * shmptr;
char result[SHM_SIZE];

//semaphore 
sem_t * full;
sem_t * mutex;
                            //semaphore


void Init()
{
    key = KEY_NUM;                  //init key
    shmid  = GetShmId(key);         // init shared memory
    shmptr = shmat(shmid,NULL,0);       // attach segement to vitural ...?
    //semaphore init
    full = sem_open(FULL_NAME,O_CREAT);
    mutex = sem_open(MUTEX_NAME,O_CREAT);
}

void ReadMessage()
{
    P(full);
    P(mutex);                       
    strcpy(result,shmptr);
    V(mutex);
}

int main(int argc, char const *argv[])
{


    Init();

    /*waiting for user to input message*/
    ReadMessage();

    printf("Receiver : message is %s\n",result);
    SemDestroy();
    printf("Receiver :  Process End \n");
    return 0;
}

4-6 init.c(初始化)

#include "common.h"

int main(int argc, char const *argv[])
{
    key_t key;
    int semid;          //semaphore id
    int shmid;          //shared memory id


    /* Create key*/
    key = KEY_NUM;

    /* Initialize Semaphore*/
    SemInit();

    /* TODO Initialize Shared Memory*/ 
    GetShmId(key);

    printf("End of initialize\n");
    return 0;
}

4-7 編譯測試 make 這裡寫圖片描述