20155339 《信息安全系统设计》第十周课下作业-IPC
2017-12-10 00:04
260 查看
20155339 《信息安全系统设计》第十周课下作业-IPC
共享内存
共享内存是在多个进程之间共享内存区域的一种进程间的通信方式,由IPC为进程创建的一个特殊地址范围,它将出现在该进程的地址空间中。使用共享内存,不同进程可以对同一块内存进行读写。
优点:由于所有进程对共享内存的访问就和访问自己的内存空间一样,而不需要进行额外系统调用或内核操作,同时还避免了多余的内存拷贝,所以,这种方式是效率最高、速度最快的进程间通信方式。
缺点:内核并不提供任何对共享内存访问的同步机制,即引发读写问题。
Linux下一般一个page大小是4k。
创建共享内存空间后,内核将不同进程虚拟地址的映射到同一个页面:所以在不同进程中,对共享内存所在的内存地址的访问最终都被映射到同一页面。
共享内存的使用过程可分为 创建->连接->使用->分离->销毁 这几步。
下图展示了共享内存的工作机制:
使用帮助文档,查看共享内存的创建函数,如下:
由上图可知:分配共享内存使用shmget函数:
使用方法如下:
int shmget(key_t key, size_t size, int shmflg)
由于帮助文档不怎么看得懂,又找到了下图进行学习:
创建后,为了使共享内存可以被当前进程使用,必须紧接着进行连接操作。使用函数shmat(SHared Memory ATtach),参数传入通过shmget返回的共享内存id。
shamt函数:连接共享内存标识符为shmid的共享内存,连接成功后把共享内存区对象映射到调用进程的地址空间,随后可像本地空间一样访问。函数原型:
void *shmat(int shmid, const void *shmaddr, int shmflg)。返回附加好的共享内存地址。
shmat函数:断开共享内存连接。
shmctl函数:共享内存管理。
代码练习(读写数据):
#include <stdio.h> #include <sys/ipc.h> #include <sys/shm.h> #include <sys/types.h> #include <unistd.h> #include <string.h> typedef struct{ char name[8]; int age; } people; int main(int argc, char** argv) { int shm_id,i; key_t key; char temp[8]; people *p_map; char pathname[30] ; strcpy(pathname,"/tmp") ; key = ftok(pathname,0x03); if(key==-1) { perror("ftok error"); return -1; } printf("key=%d\n",key) ; shm_id=shmget(key,4096,IPC_CREAT|IPC_EXCL|0600); if(shm_id==-1) { perror("shmget error"); return -1; } printf("shm_id=%d\n", shm_id) ; p_map=(people*)shmat(shm_id,NULL,0); memset(temp, 0x00, sizeof(temp)) ; strcpy(temp,"test") ; temp[4]='0'; for(i = 0;i<3;i++) { temp[4]+=1; strncpy((p_map+i)->name,temp,5); (p_map+i)->age=0+i; } shmdt(p_map) ; return 0 ; }
#include <stdio.h> #include <string.h> #include <sys/ipc.h> #include <sys/shm.h> #include <sys/types.h> #include <unistd.h> typedef struct{ char name[8]; int age; } people; int main(int argc, char** argv) { int shm_id,i; key_t key; people *p_map; char pathname[30] ; strcpy(pathname,"/tmp") ; key = ftok(pathname,0x03); if(key == -1) { perror("ftok error"); return -1; } printf("key=%d\n", key) ; shm_id = shmget(key,0, 0); if(shm_id == -1) { perror("shmget error"); return -1; } printf("shm_id=%d\n", shm_id) ; p_map = (people*)shmat(shm_id,NULL,0); for(i = 0;i<3;i++) { printf( "name:%s\n",(*(p_map+i)).name ); printf( "age %d\n",(*(p_map+i)).age ); } if(shmdt(p_map) == -1) { perror("detach error"); return -1; } return 0 ; }
运行结果
可看出读和写两段代码都是在同一段地址上进行的。
管道
管道实际是用于进程间通信的一段共享内存,创建管道的进程称为管道服务器,连接到一个管道的进程为管道客户机。一个进程在向管道写入数据后,另一进程就可以从管道的另一端将其读取出来。管道是半双工的,数据只能向一个方向流动;需要双方通信时,需要建立起两个管道。
只能用于父子进程或者兄弟进程之间(具有亲缘关系的进程)。比如fork或exec创建的新进程,在使用exec创建新进程时,需要将管道的文件描述符作为参数传递给exec创建的新进程。
数据由缓冲区的尾部写入,头部读出。先进先出原则。
PIPE函数:
代码练习
#include<stdio.h> #include<unistd.h> #include<stdlib.h> #include<string.h> #include<wait.h> #include<sys/types.h> int main(int argc ,char *argv[]) { int pipefd[2],result; char buf[1024]; int flag=0; pid_t pid; result= pipe(pipefd);//创建一个管道 if(result==-1){ perror("pipe error:"); exit(EXIT_FAILURE); } pid=fork();//创建一个子进程 if(pid==-1) { perror("fork error:"); exit(EXIT_FAILURE); } else if(pid==0){ if((close(pipefd[1]))==-1)//close write only read { perror("close write error:"); exit(EXIT_FAILURE); } while(1){ //循环读取数据 read(pipefd[0],buf,1024);//最多读取1024个字节 printf("read from pipe : %s\n",buf); if(strcmp(buf,"quit")==0){// if 读取到的字符串是exit 这是 //父进程会接受到一个终止进程的信号,父进程会回收子进程的资 // 源等 exit(EXIT_SUCCESS); } } }else{ //close read only write if((close(pipefd[0]))==-1){ perror("close read error:"); exit(EXIT_FAILURE); } while(1)//循环写入内容 { waitpid(pid,NULL,WNOHANG);//等待子进程退出 if(flag==1) exit(0); scanf("%s",buf); write(pipefd[1],buf,strlen(buf)+1);//具体写多少个字节 if(strcmp(buf,"quit")==0){ flag=1; sleep(1);//让子进程完全退出。 } } } return 1; }
FIFO(命名管道)
我觉得是管道的升级版,因为命名管道是一种特殊类型的文件,它在系统中以文件形式存在。这样克服了管道的弊端,他可以允许没有亲缘关系的进程间通信。操作方法只要创建了一个命名管道然后就可以使用open、read、write等系统调用来操作。
代码练习:
#include <unistd.h> #include <stdlib.h> #include <fcntl.h> #include <limits.h> #include <sys/types.h> #include <sys/stat.h> #include <stdio.h> #include <string.h> int main() { const char *fifo_name = "/tmp/my_fifo"; int pipe_fd = -1; int data_fd = -1; int res = 0; const int open_mode = O_WRONLY; int bytes_sent = 0; char buffer[PIPE_BUF + 1]; if(access(fifo_name, F_OK) == -1) { //管道文件不存在 //创建命名管道 res = mkfifo(fifo_name, 0777); if(res != 0) { fprintf(stderr, "Could not create fifo %s\n", fifo_name); exit(EXIT_FAILURE); } } printf("Process %d opening FIFO O_WRONLY\n", getpid()); //以只写阻塞方式打开FIFO文件,以只读方式打开数据文件 pipe_fd = open(fifo_name, open_mode); data_fd = open("Data.txt", O_RDONLY); printf("Process %d result %d\n", getpid(), pipe_fd); if(pipe_fd != -1) { int bytes_read = 0; //向数据文件读取数据 bytes_read = read(data_fd, buffer, PIPE_BUF); buffer[bytes_read] = '\0'; while(bytes_read > 0) { //向FIFO文件写数据 res = write(pipe_fd, buffer, bytes_read); if(res == -1) { fprintf(stderr, "Write error on pipe\n"); exit(EXIT_FAILURE); } //累加写的字节数,并继续读取数据 bytes_sent += res; bytes_read = read(data_fd, buffer, PIPE_BUF); buffer[bytes_read] = '\0'; } close(pipe_fd); close(data_fd); } else exit(EXIT_FAILURE); printf("Process %d finished\n", getpid()); exit(EXIT_SUCCESS); }
#include <unistd.h> #include <stdlib.h> #include <stdio.h> #include <fcntl.h> #include <sys/types.h> #include <sys/stat.h> #include <limits.h> #include <string.h> int main() { const char *fifo_name = "/tmp/my_fifo"; int pipe_fd = -1; int data_fd = -1; int res = 0; int open_mode = O_RDONLY; char buffer[PIPE_BUF + 1]; int bytes_read = 0; int bytes_write = 0; //清空缓冲数组 memset(buffer, '\0', sizeof(buffer)); printf("Process %d opening FIFO O_RDONLY\n", getpid()); //以只读阻塞方式打开管道文件,注意与fifowrite.c文件中的FIFO同名 pipe_fd = open(fifo_name, open_mode); //以只写方式创建保存数据的文件 data_fd = open("DataFormFIFO.txt", O_WRONLY|O_CREAT, 0644); printf("Process %d result %d\n",getpid(), pipe_fd); if(pipe_fd != -1) { do { //读取FIFO中的数据,并把它保存在文件DataFormFIFO.txt文件中 res = read(pipe_fd, buffer, PIPE_BUF); bytes_write = write(data_fd, buffer, res); bytes_read += res; }while(res > 0); close(pipe_fd); close(data_fd); } else exit(EXIT_FAILURE); printf("Process %d finished, %d bytes read\n", getpid(), bytes_read); exit(EXIT_SUCCESS); }
运行结果:
管道和命名管道有什么区别
1.在命名管道中,管道可以是事先已经创建好的,而在管道中,管道已经在主进程里创建好了,然后在fork时直接复制相关数据或者是用exec创建的新进程时把管道的文件描述符当参数传递进去。
2.一般来说FIFO和PIPE一样总是处于阻塞状态。如果不希望命名管道操作的时候发生阻塞,可以在open的时候使用O_NONBLOCK标志,以关闭默认的阻塞操作。
信号
一个信号的生命周期中有两个阶段:生成和传送。内核为进程生产信号,来响应不同的事件,这些事件就是信号源。主要的信号源如下:
1.异常:进程运行过程中出现异常;
2.其它进程:一个进程可以向另一个或一组进程发送信号;
3.终端中断:Ctrl-C,Ctrl-/等;
4.作业控制:前台、后台进程的管理;
5.分配额:CPU超时或文件大小突破限制;
6.通知:通知进程某事件发生,如I/O就绪等;
7.报警:计时器到期。
代码练习:
#include <stdio.h> #include <sys/types.h> #include <stdlib.h> #include <signal.h> int main() { char buffer[100]; struct sigaction act; if(sigaction(SIGINT,&act, NULL) == -1) { printf("sigaction error exit now\n"); exit(0); } while(1) { fgets(buffer,sizeof(buffer),stdin); printf("%s\n",buffer); } return 0; }
消息队列
消息队列是内核地址空间中的内部链表,通过linux内核在各个进程直接传递内容,消息顺序地发送到消息队列中,并以几种不同的方式从队列中获得,每个消息队列可以用IPC标识符唯一地进行识别。内核中的消息队列是通过IPC的标识符来区别,不同的消息队列直接是相互独立的。
每个消息队列中的消息,又构成一个独立的链表。
Linux的消息队列(queue)实质上是一个链表,它有消息队列标识符(queue ID)。
msgget创建一个新队列或打开一个存在的队列;
msgsnd向队列末端添加一条新消息;
msgrcv从队列中取消息,取消息是不一定遵循先进先出的, 也可以按消息的类型字段取消息。
代码练习:
#include<stdio.h> #include<stdlib.h> #include<sys/msg.h> #include<string.h> #include<unistd.h> #define MAX_TEXT 1024 #define MSG_SIZE 512 struct msg_st{ long int msg_type; //消息类型 char text[MAX_TEXT];//消息内容 }; int main() { struct msg_st data; char buf[MSG_SIZE]; int msgid=msgget((key_t)2456,0666|IPC_CREAT); if(msgid==-1){ perror("msgget"); exit(1); } while(1){ printf("接收:"); if(msgrcv(msgid,(void*)&data,MAX_TEXT,1,0)==-1){ perror("msgrcv"); exit(2); } printf("%s\n",data.text); } return 0; }
#include<stdio.h> #include<sys/ipc.h> #include<stdlib.h> #include<sys/types.h> #include<sys/msg.h> #include<unistd.h> #include<string.h> #define MAX_TEXT 512 #define MSG_SIZE 512 struct msg_st { long int msg_type; //消息类型 char text[MAX_TEXT];//消息内容 }; int main() { struct msg_st data; char buf[MSG_SIZE]; //创建消息队列 int msgid=msgget((key_t)2456,0666|IPC_CREAT); if(msgid==-1) { perror("msgget"); exit(1); } printf("消息队列创建成功\n"); //发送消息 while(1) { //从键盘输入发送的消息 printf("发送:"); fgets(buf,MSG_SIZE,stdin); data.msg_type=1; strcpy(data.text,buf); //将消息发送到消息队列 if(msgsnd(msgid,(void*)&data,MAX_TEXT,0)==-1){ perror("msgsnd"); exit(1); } } return 0; }
运行截图
参考链接
Linux IPC之共享内存linux编程之pipe()函数
进程间通信(IPC):FIFO
【IPC】管道和FIFO
linux c语言学习笔记之IPC-信号
IPC之消息队列详解与使用
相关文章推荐
- 20155219 第十周课下作业-IPC
- 20155321《信息安全系统设计》第十周 课下作业
- 20155326 第十周课下作业-IPC
- 2017-2018-1 20155320第十周课下作业-IPC
- 第十周课下作业-IPC
- 20155322 2017-2018-1《信息安全系统设计》第十周 课下作业-IPC
- 2017-2018-1 20155307 《信息安全系统设计基础》第十周课上未完成补充以及课下IPC作业
- 第十周java作业
- 2017-2018-1 20155331 《信息安全系统设计基础》第十周课上作业
- [Coursera 计算导论与C语言基础] 第十周作业(上)
- [Coursera 计算导论与C语言基础] 第十周作业(下)
- 第十周作业
- 第十周作业1
- 第十周博文作业:项目1 求个人所得税
- 第十周课后作业——输出10000以内回文数1
- 2016-11-19第十周作业
- 算法作业_17(2017.4.24第十周)
- 第十周作业一
- 第十周作业一
- 第十周作业【Linux微职位】