欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

Linux进程管理实验

程序员文章站 2022-04-27 11:18:49
...

实验前仔细阅读实验指导书

1.实验目的:
①加深对进程概念理解
②通过Linux管道通信机制,消息队列通信机制,共享内存通信机制使用,加深对不同类型进程通信方式理解
③加深对信号量同步机制的理解
2.实验内容:
①实现一个模拟shell:编写三个不同的程序:cmd1.c, cmd2.c, cmd3.c,每个程序输出一句话,分别编译成可执行文件cmd1, cmd2, cmd3。然后再编写一个程序,模拟shell程序的功能,能根据用户输入的字符串(表示相应的命令名),去为相应的命令创建子进程并让它去执行相应的程序,而父进程则等待子进程的结束,然后再等待接收下一条命令。如果接收到的命令为exit,则父进程结束,如果接收到无效命令,则显示”command not found”,继续等待。
②实现一个管道通信程序:由父进程创建一个管道,然后再创建3个子进程,并由这三个子进程用管道与父进程之间进行通信:子进程发送信息,父进程等三个子进程全部发完消息后再接收信息。通信的具体内容可根据自己的需要随意设计,要求能够实验阻塞型读写过程的各种情况,并要求实现进程间对管道的互斥访问。运行程序,观察各种情况下,进程实际读写的字节数以及进程阻塞唤醒情况。
③利用linux的消息队列通信机制实现两个线程间的通信:编写程序创建两个线程:sender线程和receive线程,其中sender运行函数sender(),他创建一个消息队列,然后,循环等待用户通过终端输入一串字符,将这串字符通过消息队列发送给receiver线程,直到用户输入”exit”为止;最后,它向receiver线程发送消息”end”,并且等待receiver的应答,直到应答消息后,将接收到的应答消息显示在终端上,删除相关消息队列,结束程序运行。receiver线程运行receive(),它通过消息队列接收来自sender的消息,将消息显示在终端屏幕,直到接收到”end”的消息后它向sender发送一个应答消息”over”,结束程序运行。使用无名信号量实现两个线程之间的同步与互斥。
④利用linux的共享内存通信机制实现两个进程间的通信:编写程序sender,它创建一个共享内存,然后等待用户通过终端输入一串字符,并将这串字符通过共享内存发送给receiver,最后,等待receiver应答,等到应答消息后,它接收到的应答消息显示在终端屏幕上,删除共享内存,结束程序运行。编写receiver程序,它通过共享内存接收来自sender的消息,将消息显示在终端屏幕上,然后再通过该共享内存向sender发送一个应答消息”over”,结束程序的运行。使用有名信号量或System V信号量实现两个进程对共享内存的互斥使用

实验过程以及思路:

①模拟shell
1.先去了解了下Linux中shell的工作原理

参考:https://blog.csdn.net/bit_clearoff/article/details/55058773

shell的定义:Linux系统的shell相当于操作系统的“一层外壳”,它是命令语言解释器,它为用户提供了使用操作系统的接口,它不属于内核,而是在内核之外以用户态方式运行。它的基本功能是解释并执行用户打入的各种命令,实现用户与Linux内核的接口。

启动Linux系统后,内核会为每个终端用户建立一个进程去执行shell解释程序
shell的执行步骤:
⑴读取用户由键盘输入的命令;
⑵对命令进行分析,以命令名为文件名,并将其他参数改造为系统调用execve()参数处理所要求的格式;
⑶终端进程(shell)调用fork()或者vfork()建立一个子进程;
⑷子进程根据文件名(命令名)到目录中查找有关文件,将他调入内存
⑸当子进程完成处理或者出现异常后,通过exit()或_exit()函数向父进程报告
⑹终端进程调用wait函数来等待子进程完成,并对子进程进行回收

2.exec系列函数:

参考:https://blog.csdn.net/zjwson/article/details/53337212

exec系列函数会停止执行当前的进程,并且以应用进程替换被停止执行的进程,进程 ID没有改变。
经过研究决定采用execv函数:int execv(const char *progname, char *const argv[]);
需要注意的是argv这个参数列表最后一个参数应该是NULL

经过思考后决定实现四个命令:help,echo,pwd,exit
help命令:显示所有可执行的命令
echo:字符串输出
pwd:显示当前工作目录
exit:退出shell
若输入的不是这些命令则应该有报错信息并且告知用户使用help查看所有命令

3.程序流程图
Linux进程管理实验

4.程序代码
先设计五个.c文件,根据功能调用需要的API,分别编译成help,echo,pwd,exit,err
其中echo.c需要从外部接收字符串参数

test.c
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <pwd.h>
#include <sys/wait.h>
#include <stdlib.h>
#define maxComLen 20
#define maxParam 50
#define maxHN 64
#define maxWP 128

void getPrompt(){//acquire prompt string
    char hostName[maxHN],workPath[maxWP],*p;
    int usrNameLen;
    struct passwd *userName=getpwuid(getuid());//getuid:get user id
    usrNameLen=strlen(userName->pw_name);
    printf("%s",userName->pw_name);
    if(!gethostname(hostName,maxHN))//get host name
        printf("@%s:~",hostName);
    getcwd(workPath,maxWP);//absolute path
    p=workPath+6+usrNameLen;// eliminate /home/pw_name
    printf("%s$ ",p);
}

void shell(char command[],char str[]){
    pid_t pid=fork();//return 0 for child process,child's pid for parent process
    char *arg1[]={NULL};
    char *arg2[]={str,NULL};//for echo
    if(pid==0)//create child successfully
    {
        if(!strcmp(command,"help")){
            execv("./help",arg1);   
        }
        else if (!strcmp(command,"pwd")){
            execv("./pwd",arg1);
        }
        else if (!strcmp(command,"echo")){
            execv("./echo",arg2);
        }
        else{
            execv("./err",arg1);
        }
        return;
    }   
}       



int main(){
    char command[maxComLen],str[maxParam],i;
    while(1)
    {
        getPrompt();
        scanf("%s",command);
        i=getchar();//blank or enter
        if(!strcmp(command,"exit"))exit(0);
        if(i==' '){
            fgets(str,maxParam,stdin); //Parameter for command
        }
        shell(command,str);
        wait(0);        //wait for child
    }
    return 0;
}

5.执行结果
Linux进程管理实验

6.遇到的问题及解决方案
⑴获取prompt时,使用getcwd函数获取到的是绝对路径
但是在实际的终端里,发现”/home/用户”这一传被缩略成了~符号,采取截断字符串的方法解决了该问题
⑵使用echo命令时,一开始输入字符串使用的是scanf函数,发现输出不正确
查阅资料以及调试后发现原因是scanf遇到空格便会结束,于是改用fgets函数读取

7.实验心得
⑴通过查阅资料对于fork和vfork区别理解更深刻
⑵学会以及理解了execv族函数使用
⑶学会使用int main(int argc,char *argv[])给程序传递外部参数

②管道通信
1.了解管道通信基本概念

参考:https://blog.csdn.net/rl529014/article/details/51464363
https://www.jianshu.com/p/430340c4a37a

分有名管道、无名管道两种。需要提供两个文件描述符来操作管道。其中一个对管道进行写操作,另一个对管道进行读操作。对管道的读写与一般的IO系统函数一致,使用write()函数写入数据,使用read()读出数据。FIFO。
当它访问的进程全部终止时,它也将随之被撤消。无名管道只能用在具有家族联系的进程之间。有名管道可以提供给任意关系的进程使用,但是使用不当容易导致出错
文件描述符存放了打开文件描述体的指针。
实验中采用无名管道,基本的函数如下:

#include<unistd.h>
int pipe(int filedes[2]);

创建管道,返回值:成功,返回0,否则返回-1。参数数组包含pipe使用的两个文件的描述符。fd[0]:读管道,fd[1]:写管道

ssize_t write (int fd, const void * buf, size_t count); 

write()会把参数buf所指的内存写入count个字节到参数放到所指的文件内
write时若管道满,则会引起阻塞

ssize_t read(int fd, void * buf, size_t count);

read时fd中的数据如果小于要读取的数据,就会引起阻塞。

int fcntl(int fd, int cmd, ... /* arg */ );//实现对指定文件描述符的各种操作

2.了解posix信号量

参考:https://blog.csdn.net/sicofield/article/details/10897091

分有名信号量、无名信号量两种,本实验中采用无名信号量,基本操作如下:

#include <semaphore.h>  
int sem_init(sem_t *sem, int pshared,unsigned value);//初始化,成功返回0  

参数pshared用于说明信号量的共享范围,如果pshared 为0,那么该信号量只能由初始化这个信号量的进程中的线程使用,如果pshared 非零,任何可以访问到这个信号量的进程都可以使用这个信号量。

sem_destroy(sem_t *sem);  //销毁一个信号量

int sem_wait(sem_t *sem);  //P操作
int sem_post(sem_t *sem);  //V操作

3.程序流程图
需要注意的是实验中说的是一个父进程创建三个子进程,所以需要对fork加以判断
并且对管道要互斥访问

Linux进程管理实验

4.程序代码

2test.c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <semaphore.h>
#include <sys/types.h> 
#include <fcntl.h> 
#include <sys/wait.h>
#define PIPLEN 100
sem_t sem;//semaphore
//pipe:FIFO

int pipeSize(){//measure size of pipe
  int ret,count=0,filedes[2];  
  pipe(filedes);
    fcntl(filedes[1],F_SETFL,O_NONBLOCK); //set the file state to nonblock ,nonblock writing
    while(1)  
    {  
        ret=write(filedes[1],"1",1);//write  
        if(ret==-1)  break;   //full
        count++;  
    }  
    printf("The length of unamed pipe is:%dB\n\n",count);
    close(filedes[0]);
    close(filedes[1]);
    return count;
}

void err(){
    printf("fork error!\n"); 
    exit(0);
}

int main()
{
   int fd[2],p1,p2,p3,len,actLen;      //fd[0] for read,1 for write
   char outpipe[PIPLEN],inpipe[PIPLEN];
   int maxSize=pipeSize();
   // int availableLen=maxSize;
   pipe(fd);                     //create a unamed pipe
   sem_init(&sem,1,1);//second 1:multiple processes,init sem=1
   if((p1=fork())==-1){
      err();
    }       
   if(p1==0)
   {
      sem_wait(&sem);
      close(fd[0]);       //writing,close read
      sprintf(outpipe,"This is test for child1!\n");         
      len=strlen(outpipe);
      printf("child1 process wants to write %d bytes\n",len);
      actLen=write(fd[1],outpipe,len);  //actually write in
      printf("child1 process actually writes %d bytes\n",actLen);
      //availableLen-=actLen;
      sleep(1);       //wait for a second
      sem_post(&sem);
      // if (availableLen<0)
      // {
      //    sem_post(full);//full,block reading process
      // }
      exit(0);                //child process ends
   }
   else//father
   {
       if((p2=fork())==-1){
            err();
       }
       if(p2==0)
       {
          sem_wait(&sem);
          close(fd[0]);       //writing,close read
          sprintf(outpipe,"This is test for child2!\n");         
          len=strlen(outpipe);
          printf("child2 process wants to write %d bytes\n",len);
          actLen=write(fd[1],outpipe,len);  //actually write in
          printf("child2 process actually writes %d bytes\n",actLen);
          sleep(1);
          sem_post(&sem);
          exit(0);     
       }
        else//father
        {
           if((p3=fork())==-1){
              err();
           }
           if(p3==0)
           {
              sem_wait(&sem);
              close(fd[0]);       //writing,close read
              sprintf(outpipe,"This is test for child3!\n");         
              len=strlen(outpipe);
              printf("child3 process wants to write %d bytes\n",len);
              actLen=write(fd[1],outpipe,len);  //actually write in
              printf("child3 process actually writes %d bytes\n",actLen);
              sleep(1);
              sem_post(&sem);
              exit(0);     
           }
            else//father
           {
              wait(0);           //wait for a child process
              read(fd[0],inpipe,PIPLEN);
              printf("%s\n",inpipe);
              wait(0);           //wait for a child process
              read(fd[0],inpipe,PIPLEN);
              printf("%s\n",inpipe);
              wait(0);           //wait for a child process
              read(fd[0],inpipe,PIPLEN);
              printf("%s\n",inpipe);
              exit(0);
           }
         }
     }
     return 0;
}

5.执行结果
Linux进程管理实验

6.实验中遇到的问题
⑴使用gcc编译时出现了“未定义的引用”错误
解决方案:gcc时加上-pthread
⑵一开始测试管道容量时运行非常缓慢
解决方案:改变管道读取权限为NONBLOCK无阻塞模式
⑶程序有一个bug无法解决,在父进程读取完成之后,程序无法自动结束,只能Ctrl+c手动结束

7.实验心得
⑴学会管道通信的基本框架
⑵对于fork的使用更加熟悉
⑶初步了解了posix信号量使用方法

③消息队列
1.了解消息队列基本概念

参考:https://blog.csdn.net/ljianhui/article/details/10287879
https://blog.csdn.net/yanxinrui0027/article/details/51669598

消息队列提供了一种从一个进程向另一个进程发送一个数据块的方法。 每个数据块都被认为含有一个类型,接收进程可以独立地接收含有不同类型的数据结构。我们可以通过发送消息来避免命名管道的同步和阻塞问题。若干个进程可共享一个消息队列,实现进程间消息交换。

typedef struct msgbuf
{
    long mtype; //unsigned long type,>0 
    char mtext[128]; //128,message content
}msgbuf;//消息缓冲区

int msgget(key_t key, int msgflg):
创建消息队列
函数:msgget(key_t key,int smgflag)
功能:如果参数msgflag为IPC_CREATE,则semget()新创建一个消息列

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

msgsnd:     
发送消息
int msgsend(int msgid, const void *msg_ptr, size_t msg_sz, int msgflg);
msgid是由msgget函数返回的消息队列标识符
msg_ptr是由一个指向发送消息的指针,msg_sz是消息的长度,msgflag用于控制消息队列是否满

key_t:
    IPC对象键值,每个IPC对象都关联着一个唯一的长整型的键值,
    不同进程通过相同相同的键值可访问到同一个IPC对象。
    若为0,创建一个新的消息队列,若大于0(通常通过ftok()生成的)

int msgctl(int msgid, int command, struct msgid_ds *buf);
设置消息队列属性值,若command=IPC_RMD则删队列

2.线程相关函数

参考:https://blog.csdn.net/tietao/article/details/6803742

pthread_create():
    int pthread_create(pthread_t *tidp,//ptr for pthread
    const pthread_attr_t *attr,     //attribute for pthread
    (void*)(*start_rtn)(void*),     //thread function starting address
    void *arg);                     //arguement


int pthread_join(pthread_t thread, void **retval);//等待线程的结束

3.程序流程图
Linux进程管理实验

4.程序代码

test.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>

//message buffer struct,experiment instruction book
typedef struct msgbuf
{
    long mtype; //unsigned long type,>0 
    char mtext[128]; //128,message content
}msgbuf;

//sem_t:actually long int
sem_t full;
sem_t empty;
sem_t mutex;

//messageid
int msgid;

msgbuf buf;//buffer

void *receive(void *a)
{
    //init msg
    int flag=0;
    while(1)
    {
        //sem_wait = wait
        //may cause deadlock if changing these two wait
        sem_wait(&full);//wait for sender
        sem_wait(&mutex);//mutex for message queue
        if(flag){
            sem_destroy(&full);
            sem_destroy(&empty);
            sem_destroy(&mutex);
            break;
        }
        //receive from message queue
        msgrcv(msgid,&buf,sizeof(buf.mtext),1,0);//receive mtype=1
        //print message
        printf("Receiver thread: %s\n\n",buf.mtext);
        //if received "end"
        if(strcmp(buf.mtext,"end") == 0)
        {
            flag=1;
            buf.mtype=2;//change mtype to 2 
            strcpy(buf.mtext,"over");//send the response
            msgsnd(msgid,&buf,sizeof(buf.mtext),0);
        }
        //sem_post = signal
        sem_post(&mutex);
        sem_post(&empty);

    }
    exit(1);
}

void *sender(void *a)
{   
    char s[100];
    buf.mtype = 1;
    int flag=0;
    while(1)
    {

        sem_wait(&empty);//wait for receive
        sem_wait(&mutex);//mutex for message queue
        if(flag)
        {
        // for type2
        msgrcv(msgid,&buf,sizeof(buf.mtext),2,0);
        printf("Response content: ");
        printf("%s\n",buf.mtext );//print the response 
        printf("Thanks for use!\n");
        sem_post(&full);
        sem_post(&mutex);
        break;
        }
        printf("Input the message.\n");
        scanf("%s",s);
        if(strcmp(s,"exit") == 0)//if user input "exit" to quit
        {
            flag=1;
            strcpy(s,"end");
        }
        strcpy(buf.mtext,s);
        msgsnd(msgid,&buf,sizeof(buf.mtext),0);
        printf("Sender process: %s\n",buf.mtext );
        sem_post(&full);
        sem_post(&mutex);
    }
    //IPC_RMID:remove message queue
    msgctl(msgid,IPC_RMID,NULL);
    exit(1);
}

int main()
{
    //pthread_t:thread description symbol
    pthread_t senderID; 
    pthread_t receiveID;
    key_t key = 0;//key=0 means create a new message queue
    sem_init(&full,0,0);
    sem_init(&empty,0,1);
    sem_init(&mutex,0,1);
    //S_IRUSR|S_IWUSR:allow user to read and write
    if((msgid = msgget(key, S_IRUSR|S_IWUSR)) == -1)
    {
        printf("Create Message Queue Error\n");
        exit(0);
    }  
    pthread_create(&senderID,NULL,sender,NULL);
    pthread_create(&receiveID,NULL,receive,NULL);
    /*pthread_join()
      waiting for the thread end*/
    pthread_join(senderID,NULL);
    pthread_join(receiveID,NULL);
    return 0;
}

5.执行结果
Linux进程管理实验

6.遇到的问题
⑴开始时发现输入exit后receive线程无相应
原因:一开始break在strcmp(exit)中,导致sender进程没有释放信号量直接break出循环且response无法进行

⑵要求receiver线程得到exit后向sender发送一个回应,sender接受这个回应之后删除队列,实现的
过程中发现无法输出回应信息
原因:在sender线程没有接受到消息之前,receive线程便break退出了,删除break后解决

7.实验心得
⑴了解了消息队列通信机制
⑵对同步和互斥,信号量的使用理解更深刻

④共享内存
1.了解共享内存通信机制

参考:https://blog.csdn.net/ljianhui/article/details/10253345

共享内存就是允许两个不相关的进程访问同一个逻辑内存。共享内存是在两个正在运行的进程之间共享和传递数据的一种非常有效的方式。不同进程之间共享的内存通常安排为同一段物理内存

int shmget(key_t key, size_t size, int shmflg);
创建共享内存
key:与信号量的semget函数一样,程序需要提供一个参数key(非0整数),它有效地为共享内存段命名
size:共享内存大小
semflag:IPC_CREAT, IPC_EXCL,以及IPC的指定权限位
    可以指定为IPC_CREAT | 0666其含义为,不存在则创建,访问权限为0666(新建权限默认值)

void *shmat(int shm_id, const void *shm_addr, int shmflg); 
第一次创建完共享内存时,它还不能被任何进程访问,shmat函数的作用就是用来启动    对该共享内存的访问,并把共享内存连接到当前进程的地址空间。
shm_id:是由shmget函数返回的共享内存标识。
shm_addr:指定共享内存连接到当前进程中的地址位置,通常为空,表示让系统来选择共享内存的地址。
shm_flg:是一组标志位,通常为0。
调用成功时返回一个指向共享内存第一个字节的指针

int shmdt(const void *shmaddr);
该函数用于将共享内存从当前进程中分离。注意,将共享内存分离并不是删除它,只是  使该共享内存对当前进程不再可用。失败返回-1

2.了解System V信号量

https://blog.csdn.net/anonymalias/article/details/9238705

system V信号量集(类似于数组)

对于系统中的每个System V信号量,即每个信号量集,内核都会维护一个semid_ds的信息结构
 struct semid_ds  
{  
      struct ipc_perm sem_perm;            // IPC的操作权限,每个IPC结构都有  
      __time_t sem_otime;                  //上一次执行semop() 的时间  
      unsigned long int __unused1;         //预留使用  
      __time_t sem_ctime;                  //上一次通过semctl()进行的修改时间  
      unsigned long int __unused2;  
      unsigned long int sem_nsems;         //信号量集中的信号量数目  
      unsigned long int __unused3;  
      unsigned long int __unused4;  
};  

semget(key_t key,int nsems,int semflag);
    key_t key:用于生成唯一信号量的key,主要的目的是使不同进程在同一该IPC汇合,
    unisnged long int
    nsems:表示信号量集中信号量的个数,如果创建一个信号量集,nsems必须是一个非0正整数;
    semflag:IPC_CREAT, IPC_EXCL,以及IPC的指定权限位可以指定为IPC_CREAT | 0666其       含义为,不存在则创建,访问权限为0666(新建权限默认值)
    semget的返回值是被称为信号量标识符的整数

semget之后只是得到了信号量集,并没有定义信号量,需要用户自己定义init和delete

semctl(int semid, int semnum, int cmd, .../* union semun arg*/);
信号量控制操作,失败返回-1
    semnum:表示信号量集中的第semnum个信号量
    cmd:操作命令
    arg:如果使用该参数,该参数的类型为 union semun,它是多个特定命令的联合
    union:多个成员共用一块内存

int semop(int semid, struct sembuf *sops, unsigned nsops);
信号量操作函数(P,V)
    struct sembuf  
    {  
      unsigned short int sem_num;   /* 信号量的序号从0~nsems-1 */  
      short int sem_op;            /* 对信号量的操作,>0, 0, <0 */  
      short int sem_flg;            /* 操作标识:0, IPC_WAIT, SEM_UNDO */  
    }
    sem_num标识信号量集中的第几个信号量,0表示第1个,1表示第2个nsems - 1表示最后一个
    sem_op=-1   P操作
    sem_op=1    V操作
    nsops:sops所指向sembuf结构体数组中元素的个数
    semval:信号量的当前值

3.程序流程图
与消息队列中的基本一致,只是初始化消息队列变成了初始化共享内存

4.源代码

4.源代码
common.h
#include <sys/sem.h>
#include <stdlib.h>
#include <unistd.h>
#include <stdio.h>
#include <string.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#define BUFFSIZE  2048

struct shm_buff
{
    char buffer[BUFFSIZE];
};

union semun
{
    int val;// Value for SETVAL 
    struct semid_ds *buf; //Buffer for IPC_STAT, IPC_SET
};


/*init*/
void init_sem(int sem_id,int init_value)
{
    union semun sem_union;
    sem_union.val=init_value;  /*init_value*/
    if(semctl(sem_id,0,SETVAL,sem_union)==-1)
    {
        printf("Initialize error!\n");
    }
}

/*delete semephore*/
void del_sem(int sem_id)
{
    union semun sem_union;
    if(semctl(sem_id,0,IPC_RMID,sem_union)==-1)//IPC_RMID to delete
    {
        printf("Delete sem error!\n");
    }
}

/*wait*/
void Wait(int sem_id)
{
    struct sembuf a;
    a.sem_num=0;  /*just one semaphore=0*/
    a.sem_op=-1;  /*represents P operation*/
    if(semop(sem_id,&a,1)==-1)
    {
        printf("P operationn error!\n");
    }
}

/*signal*/
void Signal(int sem_id)
{
    struct sembuf a;
    a.sem_num=0;  
    a.sem_op=1;  /*represents V operation*/
    if(semop(sem_id,&a,1)==-1)
    {
        printf("V operationn error!\n");
    }
}

sender.c
#include"common.h"
int main()
{
    char *sm;
    struct shm_buff *sb;
    int shmid,semid;

    //create a mutex for visiting shared memory
    semid=semget(10,1,0666|IPC_CREAT);
    init_sem(semid,1); //init as 1

    //create shared memory
    shmid=shmget(20,sizeof(struct shm_buff),0666|IPC_CREAT);
    if(shmid==-1)
    {
        printf("Create shared memory failed!\n");
        del_sem(semid);
        exit(1);
    }

    //address map
    sm=shmat(shmid,NULL,0);
    //sm:the first ptr of shared memory space
    sb=(struct shm_buff *)sm;

    do
    {
        Wait(semid);
        printf("Send message to receiver:");
        //input messages
        fgets(sb->buffer,sizeof(sb->buffer),stdin);
        Signal(semid);
    }while(strncmp(sb->buffer,"exit",4)!=0);

    //after user input "exit"

    //break the loop then delete the semaphore
    del_sem(semid);

    //seperate the current process from the shared memory
    if(shmdt(sm)==-1)
    {
        printf("Seperate the sender error!\n");
        exit(1);
    }

    exit(0);
}

receiver.c
#include"common.h"

int main()
{
    char *sm=NULL;
    struct shm_buff *sb;
    int shmid,semid;

    //key value must be the same as that in sender.c
    semid=semget(10,1,0666);
    if(semid==-1)
    {
        printf("Sorry,no sender!\n");
        exit(1);
    }

    //key value must be the same as that in sender.c
    shmid=shmget(20,sizeof(struct shm_buff),0666|IPC_CREAT);

    if(shmid==-1)
    {
        printf("Create shared memory error!\n");
        exit(1);
    }

    sm=shmat(shmid,NULL,0);
    sb=(struct shm_buff *)sm;

    do
    {
        Wait(semid);
        printf("Message content from sender: %s",sb->buffer);
        if(strncmp(sb->buffer,"exit",4)==0)
        {
            //recieve exit
            printf("Thanks for use,goodbye!\n");
            exit(0);
        }
        memset(sb->buffer,0,BUFFSIZE);
        Signal(semid);
    }while(1);

    //seperate the current process from the shraed memory
    if(shmdt(sm)==-1)
    {
        printf("Seperate the reciever error!\n");
        exit(1);
    }

    //delete shared memory after receiving "exit"
    if(shmctl(shmid,IPC_RMID,NULL)==-1)
    {
        printf("Delete the shared memory error!\n" );
        exit(1);
    }
    exit(0);
}

5.执行结果
在sender终端中输入消息,在对应的receiver终端显示刚刚输入的消息,若为exit则退出
Linux进程管理实验

6.实验中问题
⑴访问权限不加上初始化错误,加*问权限后解决,0666|IPC_CREAT
⑵只有先运行sender再运行receiver才能成功,因为receiver中没有CREAT,否则会报错
⑶两个进程中的getmutex和getSharedMem的key要一致,不然无法接受消息成功

7.实验收获
⑴了解与使用SYSTEM V信号量
⑵理解了共享内存消息通信机制

实验后的思考与改进
1.如果实验1中命令过多,if过多怎么办?
可以将所有命令存在一个数组里,再写一个函数,查找指令在数组中对应的下标,再使用case语句判断下标从而得到不同的命令。
2.并行程序流程图画法:一对平行线
Linux进程管理实验
3.管道通信是否是双向的?
中用pipe函数创建的管道是双向的,一个用来读,一个用来写
4.文件描述符如何跟索引(i节点)建立关系(从而找到文件的位置)?
Linux进程管理实验
5.为什么共享内存通信效率高
共享内存和消息队列,FIFO,管道传递消息的区别:
后者,消息队列,FIFO,管道的消息传递方式一般为
1:服务器得到输入
2:通过管道,消息队列写入数据,通常需要从进程拷贝到内核。
3:客户从内核拷贝到进程
4:然后再从进程中拷贝到输出文件
上述过程通常要经过4次拷贝,才能完成文件的传递。
而共享内存只需要
1:从输入文件到共享内存区
2:从共享内存区输出到文件
上述过程不涉及到内核的拷贝,所以花的时间较少。