# 2017-2018-1 20155302 课下实践IPC

2017-2018-1 20155302 课下实践IPC

共享内存

共享内存允许两个或多个进程共享一定的存储区,因为不需要拷贝数据,所以这是最快的一种IPC。

共享内存是在多个进程之间共享内存区域的一种进程间的通信方式,由IPC为进程创建的一个特殊地址范围,它将出现在该进程的地址空间(这里的地址空间具体是哪个地方?)中。其他进程可以将同一段共享内存连接到自己的地址空间中。所有进程都可以访问共享内存中的地址,就好像它们是malloc分配的一样。如果一个进程向共享内存中写入了数据,所做的改动将立刻被其他进程看到。

共享内存是IPC最快捷的方式,因为共享内存方式的通信没有中间过程,而管道、消息队列等方式则是需要将数据通过中间机制进行转换。共享内存方式直接将某段内存段进行映射,多个进程间的共享内存是同一块的物理空间,仅仅映射到各进程的地址不同而已,因此不需要进行复制,可以直接使用此段空间。

#include <sys/ipc.h>
#include <sys/shm.h>
(1)创建或访问共享内存
    * int shmget(key_t key,size_t size,int shmflg);

(2)附加共享内存到进程的地址空间
    * void *shmat(int shmid,const void *shmaddr,int shmflg);//shmaddr通常为NULL,由系统选择共享内存附加的地址;shmflg可以为SHM_RDONLY

(3)从进程的地址空间分离共享内存
    * int shmdt(const void *shmaddr); //shmaddr是shmat()函数的返回值

(4)控制共享内存
    * int shmctl(int shmid,int cmd,struct shmid_ds *buf);
    * struct shmid_ds{
          struct ipc_perm shm_perm;
          …
      };
cmd的常用取值有:(a)IPC_STAT获取当前共享内存的shmid_ds结构并保存在buf中(2)IPC_SET使用buf中的值设置当前共享内存的shmid_ds结构(3)IPC_RMID删除当前共享内存

代码实例:

建立共享内存并写入数据的程序

#include <stdio.h>
#include <string.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <stdlib.h>
#include <errno.h>
void get_buf(char *buf)
{
    int i=0;
    while((buf[i]=getchar())!=‘\n‘&&i<1024)
        i++;
}
int main(void)
{
    int shmid;
    shmid=shmget(IPC_PRIVATE,sizeof(char)*1024,IPC_CREAT|0666);
    if(shmid==-1)
    {
        perror("shmget");
    }
    char *buf;
    if((int)(buf=shmat(shmid,NULL,0))==-1)
    {
        perror("shmat");
        exit(1);
    }
    get_buf(buf);
    printf("%d\n",shmid);
    return 0;
}

读取数据的程序

#include <stdio.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <stdlib.h>
int main(int argc,char **argv)
{
    int shmid;
    shmid=atoi(argv[1]);
    char *buf;
    if((int)(buf=shmat(shmid,NULL,0))==-1)
    {
        perror("shmat");
        exit(1);
    }
    printf("%s\n",buf);
    shmdt(buf);
    return 0;
}

管道

管道的特点:

1、管道是半双工的,数据只能向一个方向流动;需要双方通信时,需要建立起两个管道;

2、只能用于父子进程或者兄弟进程之间(具有亲缘关系的进程)。比如fork或exec创建的新进程,在使用exec创建新进程时,需要将管道的文件描述符作为参数传递给exec创建的新进程。当父进程与使用fork创建的子进程直接通信时,发送数据的进程关闭读端,接受数据的进程关闭写端。

3、单独构成一种独立的文件系统:管道对于管道两端的进程而言,就是一个文件,但它不是普通的文件,它不属于某种文件系统,而是自立门户,单独构成一种文件系统,并且只存在与内存中。

4、数据的读出和写入:一个进程向管道中写的内容被管道另一端的进程读出。写入的内容每次都添加在管道缓冲区的末尾,并且每次都是从缓冲区的头部读出数据。

(1)在两个程序之间传递数据的最简单的方法是使用popen()和pclose()函数:
    #include <stdio.h>
    FILE *popen(const char *command, const char *open_mode);
    int pclose(FILE *stream);
popen()函数首先调用一个shell,然后把command作为参数传递给shell。这样每次调用popen()函数都需要启动两个进程;但是由于在Linux中,所有的参数扩展(parameter expansion)都是由shell执行的,这样command中包含的所有参数扩展都可以在command程序启动之前完成。

(2)pipe()函数:
    #include <unistd.h>
    int pipe(int pipefd[2]);
popen()函数只能返回一个管道描述符,并且返回的是文件流(file stream),可以使用函数fread()和fwrite()来访问。pipe()函数可以返回两个管道描述符:pipefd[0]和pipefd[1],任何写入pipefd[1]的数据都可以从pipefd[0]读回;pipe()函数返回的是文件描述符(file descriptor),因此只能使用底层的read()和write()系统调用来访问。pipe()函数通常用来实现父子进程之间的通信。

(3)命名管道:FIFO
    #include <sys/types.h>
    #include <sys/stat.h>
    int mkfifo(const char *fifo_name, mode_t mode);

代码实例:

read端

#include<stdlib.h>
#include<stdio.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<fcntl.h>
#include<errno.h>
#define PATH "./fifo"
#define SIZE 128
int main()
{
 umask(0);
 if (mkfifo (PATH,0666|S_IFIFO) == -1)
 {
 perror ("mkefifo error");
 exit(0);
 }
 int fd = open (PATH,O_RDONLY);
 if (fd<0)
 {
  printf("open fd is error\n");
  return 0;
 } 

 char Buf[SIZE];
 while(1){
 ssize_t s = read(fd,Buf,sizeof(Buf));
 if (s<0)
 {
  perror("read error");
  exit(1);
 }
 else if (s == 0)
 {
  printf("client quit! i shoud quit!\n");
  break;
 }
 else
 {
  Buf[s] = ‘\0‘;
  printf("client# %s ",Buf);
  fflush(stdout);
 }
 }
 close (fd);
 return 3;
} 

write端

#include<stdlib.h>
#include<stdio.h>
#include<unistd.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<string.h>
#include<errno.h>
#include<fcntl.h> 

#define PATH "./fifo"
#define SIZE 128
int main()
{
 int fd = open(PATH,O_WRONLY);
 if (fd < 0)
 {
  perror("open error");
  exit(0);
 } 

 char Buf[SIZE];
 while(1)
 {
  printf("please Enter#:");
  fflush(stdout);
  ssize_t s = read(0,Buf,sizeof(Buf));
  if (s<0)
  {
   perror("read is failed");
   exit(1);
  }
  else if(s==0)
  {
   printf("read is closed!");
   return 1;
  }
  else{
   Buf[s]= ‘\0‘;
   write(fd,Buf,strlen(Buf));
  }
 }
 return 0;
} 

FIFO(命名管道)

管道和命名管道的区别:

对于命名管道FIFO来说,IO操作和普通管道IO操作基本一样,但是两者有一个主要的区别,在命名管道中,管道可以是事先已经创建好的,比如我们在命令行下执行

mkfifo myfifo

就是创建一个命名通道,我们必须用open函数来显示地建立连接到管道的通道,而在管道中,管道已经在主进程里创建好了,然后在fork时直接复制相关数据或者是用exec创建的新进程时把管道的文件描述符当参数传递进去。

一般来说FIFO和PIPE一样总是处于阻塞状态。也就是说如果命名管道FIFO打开时设置了读权限,则读进程将一直阻塞,一直到其他进程打开该FIFO并向管道写入数据。这个阻塞动作反过来也是成立的。如果不希望命名管道操作的时候发生阻塞,可以在open的时候使用O_NONBLOCK标志,以关闭默认的阻塞操作。

FIFO可以说是管道的推广,克服了管道无名字的限制,使得无亲缘关系的进程同样可以采用先进先出的通信机制进行通信。

管道和FIFO的数据是字节流,应用程序之间必须事先确定特定的传输"协议",采用传播具有特定意义的消息。

要灵活应用管道及FIFO,理解它们的读写规则是关键。

代码实例:

接收消息:

  #include                 <stdio.h>
  #include                 <sys/types.h>
  #include                 <sys/stat.h>
  #include                 <string.h>
  #include                 <fcntl.h>
  #include                 <errno.h>
  #include                 <sys/time.h>
  #include                 <unistd.h>   

  #define   FIFO         "/tmp/fifo.temp1"
  #define   MAXLINE   1024   

  int   main(void)
  {
       int           fifo,   fd;
       char         buf[MAXLINE];
       int           len;
       fd_set     set;
       struct     timeval   tv;
       int           i   =   0;   

       unlink(FIFO);   //如果FIFO存在,就先删除
       if   ((fifo   =   mkfifo(FIFO,   O_RDWR))   <   0)       //产生一个有名管道
       {
            printf("mkfifo   error:   %s/n",   strerror(errno));
            return(0);
       }
       if   ((fd   =   open(FIFO,   O_RDWR))   <   0)               //读写打开有名管道
       {
            printf("open   error:   %s/n",   strerror(errno));
            return(0);
       }
       FD_ZERO(&set);
       FD_SET(fd,   &set);
       tv.tv_sec   =   5;
       tv.tv_usec   =   0;   //超时设置,超过5秒没有信息,就打印超时
       while   (1)
       {
            FD_SET(fd,   &set);
            if   ((i   =   select(fd   +   1,   &set,   NULL,   NULL,   &tv))   >   0)//检测管道是否信息
            {
                printf("receive   data/n");
                if   (FD_ISSET(fd,   &set))
                {
                                len   =   read(fd,   buf,   MAXLINE);//读取信息
                                buf[len]   =   ‘/0‘;
                                printf("buf   =   %s/n",   buf);
                                tv.tv_sec   =   atoi(buf);
                                tv.tv_usec   =   0;
                }
            }
            else   if   (i   ==   0)
            {
                 tv.tv_sec   =   5;
                 tv.tv_usec   =   0;
                 printf("chaoshi/n");
            }
            else
                 printf("error/n");
       }   

       unlink(FIFO);     //删除有名管道
       return(0);
  }  

发消息:

  #include                 <stdio.h>
  #include                 <sys/types.h>
  #include                 <sys/stat.h>
  #include                 <string.h>
  #include                 <fcntl.h>
  #include                 <errno.h>   

  #define   FIFO         "/tmp/fifo.temp1"
  #define   MAXLINE   1024   

  int   main(void)
  {
        int           fifo;
        char         buf[MAXLINE];
        int           len;
        int           i   =   0;   

        strcpy(buf,   "10");
        if   ((fifo   =   open(FIFO,   O_RDWR))   <   0)                   //读写打开有名管道
        {
             printf("mkfifo   error:   %s/n",   strerror(errno));
             return(0);
        }
        while   (i   <   10)
        {
             sprintf(buf,   "%d",   i   +   1);
             len   =   write(fifo,   buf,   strlen(buf));       //写入信息到管道中
             printf("send   len   =   %d/n",   len);
             sleep(i);
             i++;
        }   

        return(0);
  }   

信号

信号是Unix/Linux系统在一定条件下生成的事件。信号是一种异步通信机制,进程不需要执行任何操作来等待信号的到达。信号异步通知接收信号的进程发生了某个事件,然后操作系统将会中断接收到信号的进程的执行,转而去执行相应的信号处理程序。

(1)注册信号处理函数
    #include <signal.h>
    /*typedef void (*sighandler_t)(int);  sighandler_t signal(int signum,sighandler_t handler);*/
    * void (*signal(int signum, void (*handler)(int)))(int);  //SIG_IGN && SIG_DFL
    * int sigaction(int signum, const struct sigaction *act,struct sigaction *oldact);

(2)发送信号
    #include <signal.h>
    * int kill(pid_t pid,int sig); //#include <sys/types.h>
    * int raise(int sig);            //kill(getpid(),sig);
    * unsigned int alarm(unsigned int seconds); //(#include <unistd.h>) seconds秒后,向进程本身发送SIGALRM信号。

(3)信号集
    信号集被定义为:typedef struct {unsigned long sig[_NSIG_WORDS];} sigset_t;
    * int sigaddset(sigset_t *set,int sig);
    * int sigemptyset(sigset_t *set);

信号是进程间通信机制中唯一的异步通信机制,可以看作是异步通知,通知接收信号的进程有哪些事情发生了。信号机制经过POSIX实时扩展后,功能更加强大,除了基本通知功能外,还可以传递附加信息。

代码实例:

#include <signal.h>
#include <stdio.h>

void int_handler(int signum)
{
 printf("\nSIGINT signal handler.\n");
 printf("exit.\n");
 exit(-1);
}

int main()
{
 signal(SIGINT, int_handler);
 printf("int_handler set for SIGINT\n");

 while(1)
 {
  printf("go to sleep.\n");
  sleep(60);
 }

 return 0;
}

消息队列

消息队列是内核地址空间中的内部链表,通过linux内核在各个进程直接传递内容,消息顺序地发送到消息队列中,并以几种不同的方式从队列中获得,每个消息队列可以用IPC标识符唯一地进行识别。内核中的消息队列是通过IPC的标识符来区别,不同的消息队列直接是相互独立的。每个消息队列中的消息,又构成一个独立的链表。

消息队列克服了信号承载信息量少,管道只能承载无格式字符流。

消息队列保存在内核中,是一个由消息组成的链表。

#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
(1)创建或访问消息队列
* int msgget(key_t key,int msgflg);

(2)操作消息队列
    * int msgsnd(int msqid,const void *msg,size_t nbytes,int msgflg);
msg指向的结构体必须以一个long int成员开头,作为msgrcv()的消息类型,必须大于0。nbytes指的是msg指向结构体的大小,但不包括long int部分的大小
    * ssize_t msgrcv(int msqid,void *msg,size_t nbytes,long msgtype,int msgflg);
如果msgtype是0,就返回消息队列中的第一个消息;如果是正整数,就返回队列中的第一个该类型的消息;如果是负数,就返回队列中具有最小值的第一个消息,并且该最小值要小于等于msgtype的绝对值。

(3)控制消息队列
    * int msgctl(int msqid,int cmd,struct msqid_ds *buf);
    * struct msqid_ds{
          struct ipc_perm msg_perm;
          …
       };

代码实例:

#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#include <stdio.h>
#define MAX_LINE 80
#define MY_MQ_ID 1233
/*消息结构体的一般形式如下:
  typedef struct
  {
    long type;   //用于存放消息代码,必须位于首位
    char message[ LENGHT+1 ];
  }MSG_TYPE_T;
 */
typedef struct
{
    long type;
    float fval;
    unsigned int uival;
    char strval[ MAX_LINE+1 ];
}MY_TYPE_T;
int main(  )
    {
        int msgid,ret;
        //create the message queue with the id MY_MQ_ID
        msgid=msgget( MY_MQ_ID,0666|IPC_CREAT );
        if( msgid>=0 )
             printf( "Created a Message Queue,message queue identifier is %d\n",msgid );
        //modify the size of message queue
        struct msqid_ds buf;
        ret=msgctl( msgid,IPC_STAT,&buf );
        printf( "The origianl size of queue is %d\n",buf.msg_qbytes );   

        buf.msg_qbytes=4096;
        ret=msgctl( msgid,IPC_SET,&buf );
        if( ret==0 )
            printf( "Size sucessfully changed for queue,message queue identifier is %d\n",msgid );
        //send a message
        MY_TYPE_T myMessage;
        myMessage.type=1L;   //消息的类型,msgrcv会用到
        myMessage.fval=128.256;
        myMessage.uival=512;
        strncpy( myMessage.strval,"This is a test.\n",MAX_LINE );
        ret=msgsnd( msgid,( struct msgbuf* )&myMessage,sizeof( MY_TYPE_T ),0 ); //0是消息旗标
        if( ret!=-1 )
            printf( "Message send successfully.\n" );
        //read a message
        MY_TYPE_T recMessage;
        ret=msgrcv( msgid,( struct msgbuf* )&recMessage,sizeof(MY_TYPE_T),1,0 );//这个地方Message Type要和欲接受的消息类型相同
        if( ret!=-1 )
            {
                printf( "\nRead a message from the queue\n" );
                printf( "Message Type:%ld\n",recMessage.type );
                printf( "Float value:%f\n",recMessage.fval );
                printf( "Uint value:%d\n",recMessage.uival );
                printf( "String value:%s\n",recMessage.strval );
            }
        //destroy a message queue
        ret=msgctl( msgid,IPC_RMID,NULL );
        if( ret!=-1 )
            printf( "Message queue %d sucessfully removed.\n",msgid );   

        return 0;
    }
时间: 2024-10-10 19:03:01

# 2017-2018-1 20155302 课下实践IPC的相关文章

2017-2018-1 20155226 《信息安全系统设计基础》课下实践——实现mypwd

2017-2018-1 20155226 <信息安全系统设计基础>课下实践--实现mypwd 1 学习pwd命令 输入pwd命令 发现他是给出当前文件夹的绝对路径. 于是 man 1 pwd查看pwd详细 然后查看pwd实现需要的系统调用man -k; grep 在这发现了一个功能相同的内核函数getcwd 到这步就很简单了,先查看这个函数man getcwd 2 写出伪代码 char一个数组: 调用内核函数[getcwd()]获取当前目录的绝对路径并保存至数组中: if(返回的指针==NUL

2018.3.8课下作业

2018.3.8课下作业 相关知识点的总结 编写代码 mkdir用于新建文件夹 vi用于编写代码 esc:wq结束编写 上传码云 git add+文件名 git commit -m "说明" git push -f origin master pakeage注意 javac src/Hello.java -d bin编写至bin文件夹 java -cp bin is5227.Hello在bin文件夹中编译 课上内容的补做 作业:p14 结果截图 代码的功能:规定数的求和 作业:带包的代

课下实践——实现Mypwd

实现Mypwd 学习pwd命令 想要知道当前所处的目录,可以用pwd命令,该命令显示整个路径名. L 目录连接链接时,输出连接路径 P 输出物理路径 研究pwd实现需要的系统调用(man -k; grep),写出伪代码 内核为每个目录都设置了一个指向自己的i节点入口,即".",还有一个指向其父目录i节点的入口,即"..",我们首先获取当前目录的i节点编号,但是并不能知道当前目录的名称,我们切换到其的父目录,在里面寻找当前i节点编号对应的文件名即可.这样我们就很容易联

2017-2018-1 20155330 《信息安全系统设计基础》课堂测试&amp;课下作业

2017-2018-1 20155330 <信息安全系统设计基础>课堂测试&课下作业 stat命令的实现-mysate 学习使用stat(1),并用C语言实现 提交学习stat(1)的截图 man -k ,grep -r的使用 伪代码 产品代码 mystate.c,提交码云链接 测试代码,mystat 与stat(1)对比,提交截图 STAT(1)学习 使用man 1 stat命令查看 使用man -k stat | grep 2查找相关函数 man 2 stat查看stat函数 产品

android从放弃到坚持放弃第二课(下)

续第二课( 下) 续第二课 下 活动的生命周期 返回栈 活动状态 活动的生存期 体验活动的生命周期 活动被回收怎么办 活动的启动模式 standard singleTop singleTask singleInstance 实践出真知 知晓当前是哪一个活动 随时随地退出程序 启动活动的最佳写法 问题 写app必须掌握活动的生命周期. [活动的生命周期] [返回栈] android每次启动的活动会覆盖在原活动之上,然后点击Back键会销毁最上层的活动.是使用Task来管理活动,一个任务就是一组存放

2017-2018-1 20155318 《信息安全系统设计基础》第十周课上测试及课下作业

2017-2018-1 20155318 <信息安全系统设计基础>第十周课上测试及课下作业 课上测试 解析:填充消除了冲突不命中,对于x和y数组,只有在引用第0个和第4个元素的时候发生不命中.因而命中率为75%. 解析:高速缓存容量为2048,高速缓存结构为(( 32 ),8,8,32) 解析:不同层之间是以块为大小传输单元在层与层之间复制,空缓存的不命中叫强制性不命中或冷不命中 解析:存储器层次结构的每一层都缓存来自较低一层的数据.缓存存储器是分块的,数据总是以块为基本单位在每一层之间传递,

20155332 补交ch12课下作业

20155332 补交ch12课下作业 课下测试提交晚了,我课后补做了一遍,答对13题,答错3题. 试题内容如下所示: 课本内容 1.并发(Concurrency) 访问慢I/O设备:就像当应用程序等待I/O中的数据时内核会切换运行其他进程一样,我们的应用也可以用类似的方式,将I/O请求与其他工作重叠从而挖掘并发的潜能. 推迟工作而减少延迟:我们可以推迟一些耗时工作稍后执行,例如内存分配器不在free时整理碎片,而是将这些琐屑的工作推迟到一个稍后执行的独立"逻辑流"(logical f

# 20155337 2017-2018 1 课上测试、课下作业、实验

20155337 2017-2018 1 课上测试.课下作业.实验 第一周 学习总结 第二周 课堂实践 第三周 学习总结 第四周 学习总结+myod 第五周 学习总结 课堂实践 第六周 学习总结 实验一 第七周 学习总结 实验二 第八周 加分项目pwd 第九周 学习总结 第十周 实验三 第十一周 学习总结

20165315 第二次考试课下补做

20165315 第二次考试课下补做 课上内容的补做,结果截图 参考http://www.cnblogs.com/rocedu/p/6766748.html 编程实现1!+2!+3!+... + N!的功能,N由命令行传入,比如类名为SumofRecur, java SumofRecur 8 给出1!+2!+3!+... + 8!的值, 提交运行结果的截图(至少五张),注意测试正常,异常,边界情况, 比如java SumofRecur -8,java SumofRecur 0,java Sumo