第十五章 IPC
2016-07-21 16:32
316 查看
1. 管道
局限性:历史上,它们是半双工;管道只能在具有公共祖先的两个进程间使用。
int pipe(int fd[2]); //创建管道
fd[0]为读而打开,fd[1]为写而打开;fd[1]的输出是fd[0]的输入。
i. 当读一个写端已被关闭的管道时,在所有数据都被读取后,read返回0,表示文件结束
ii. 当写一个读端已被关闭的管道时,则产生信号SIGPIPE。
//每次一页的显示已产生的输出
2. popen函数和pclose函数
//创建一个管道,fork一个子进程,关闭未使用的管道段,执行一个shell运行命令,然后等待命令终止
FILE *popen(const char *cmdstring, const char* type);
int pclose(FILE* fp);
//将1中的第二个程序重新写
popen和pclose函数
//一个应用程序,它向标准输出写一个提示,然后从标准输入读1行。使用popen,可以在应用程序和输入之间插入一个程序一遍对输入进行变换控制
3. 协同进程
//当一个过滤程序既产生某个过滤程序的输入,又读取该过滤程序的输出时,它就变成了协同进程
//一个简单的协同进程,它从标准输入读取两个数,计算它们的和,然后将和写至其标准输出
4. FIFO(命名管道)
未命名管道只能在两个相关的进程之间,而且这两个的进程还要有一个共同创建了它们的祖先进程;但是,通过FIFO,不相关的进程也能交换数据
int mkfifo(const char *path, mode_t mode);
int mkfifoat(int fd, const char *path, mode_t mode);
用途:
i. shell命令使用FIFO将数据从一条管道传送到另一条时,无需创建中间临时文件;
ii. 客户进程-服务器进程应用程序中,FIFO用作汇聚点,在客户进程和服务进程二者之间传递数据。
5. XSI的IPC
有三种称作XSI的IPC的IPC: 消息队列,信号量,以及共享存储器
i. 标识符和键
标识符是IPC对象的内部名。为使多个合作进程能够在同一IPC对象上汇聚,需要提供一个外部命名方案。为此,每个IPC对象都与一个键相关联,将这个键作为该对象的外部名。
ii. 权限结构
iii. 结构限制
6. 消息队列
int msgget(key_t key, int flag); //打开一个现有队列或创建一个新队列
int msgctl(int msqid, int cmd, struct msqid_ds *buf); //根据cmd的不同,对队列进行多种操作
int msgsnd(int msqid, const void *ptr, size_t nbytes, int flag); //将数据放入消息队列中
ssize_t msgrcv(int msqid, void *ptr, size_t nbytes, long type, int flag); //从队列中取消息
//type == 0,返回队列中的第一个消息;type > 0,返回队列中消息类型为type的第一个消息;type < 0,返回队列中消息类型值小于等于type绝对值的消息。
7. 信号量
它是一个计数器,用于为多个进程提供对共享数据对象的访问。
int semget(key_t key, int nsems, int flag); //获得一个信号量ID
int semctl(int semid, int semnum, int cmd, .../* union semum arg */); //根据cmd的不同,执行不同的操作
int semop(int semid, struct sembuf semoparray[], size_t nops); //自动执行信号量集合上的操作数组
8. 共享存储
共享存储允许两个或多个进程共享一个给定的存储区。因为数据不需要在客户进程和服务器进程之间复制,所以这是最快的一种IPC。
int shmget(key_t key, size_t size, int flag); //获得一个共享存储标识符
int shmctl(int shmid, int cmd, struct shmid_ds *buf); //根据cmd的不同,进行不同的操作
void *shmat(int shmid, const void *addr, int flag); //一旦创建了一个共享存储段,将其连接到它的地址空间中
//addr为0 ,则此段连接到内核选择的第一个可用地址上;若非0,则连接到指定地址上
int shmdt(const void *arr); //当对共享存储段的操作已经结束时,与该段分离
//看共享存储段所存放的位置
/dev/zero的存储映射
匿名存储映射(根据上面的程序进行修改)
i. 删除了/dev/zero的open语句; ii. 删除了fd的close语句; iii. 将mmap调用修改:
if((area = mmap(0, SIZE, PROT_READ | PROT_WRITE, MAP_ANON | MAP_SHARED, -1, 0)) == MAP_FAILED)
9. POSIX信号量
有两种形式:命名的和未命名的。 未命名的信号量只存在于内存中,并要求使用信号量的进程必须访问内存。命名信号量可以通过名字访问,因此可以被任何已知它们名字的进程中的线程使用。
//创建一个新的命名信号量或者使用一个现有信号量
sem_t *sem_open(const char *name, int oflag, ... /* mode_t mode, unsigned int value */);
int sem_close(sem_t *sem); //释放任何信号量相关的资源
int sem_unlink(const char *name); //销毁一个命名信号量
int sem_trywait(sem_t *sem); int sem_wait(sem_t *sem); //实现信号量减1操作
int sem_timedwait(sem_t *restrict sem, const struct timespec *restrict tsptr);
int sem_post(sem_t *sem); //使信号量值增1
int sem_init(sem_t *sem, int pshared, unsigned int value); //创建一个未命名的信号量
int sem_destroy(sem_t *sem); //未命名的信号量的使用完成时,对其丢弃
int sem_getvalue(sem_t *restrict sem, int *restrict valp); //检索信号量值
基于信号量的互斥原语的实现
10. 客户进程-服务器进程属性
局限性:历史上,它们是半双工;管道只能在具有公共祖先的两个进程间使用。
int pipe(int fd[2]); //创建管道
fd[0]为读而打开,fd[1]为写而打开;fd[1]的输出是fd[0]的输入。
i. 当读一个写端已被关闭的管道时,在所有数据都被读取后,read返回0,表示文件结束
ii. 当写一个读端已被关闭的管道时,则产生信号SIGPIPE。
<span style="font-size:18px;">#include "apue.h" int main(void) { int n; int fd[2]; pid_t pid; char line[MAXLINE]; if(pipe(fd) < 0) err_sys("pipe error"); if((pid = fork()) < 0){ err_sys("fork error"); } else if(pid == 0){ close(fd[0]); write(fd[1], "hello world\n", 12); } else{ close(fd[1]); n = read(fd[0], line, MAXLINE); write(STDOUT_FILENO, line, n); } exit(0); } </span>
//每次一页的显示已产生的输出
<span style="font-size:18px;">#include "apue.h" #include <sys/wait.h> #define DEF_PAGER "/bin/more" int main(int argc, char *argv[]) { int n; int fd[2]; pid_t pid; char *pager, *argv0; char line[MAXLINE]; FILE *fp; if(argc != 2) err_quit("usage: a.out <pathname>"); if((fp = fopen(argv[1], "r")) == NULL) err_sys("can't open %s", argv[1]); if(pipe(fd) < 0) err_sys("pipe error"); if((pid = fork()) < 0){ err_sys("fork error"); } else if(pid > 0){ close(fd[0]); //parent copies argv[1] to pipe while(fgets(line, MAXLINE, fp) != NULL){ n = strlen(line); if(write(fd[1], line, n) != n) err_sys("write error to pipe"); } if(ferror(fp)) err_sys("fgets error"); close(fd[1]); if(waitpid(pid, NULL, 0) < 0) err_sys("waitpid error"); exit(0); } else{ close(fd[1]); if(fd[0] != STDIN_FILENO){ if(dup2(fd[0], STDIN_FILENO) != STDIN_FILENO) err_sys("dup2 error to stdin"); close(fd[0]); // ??? don't need this after dup2 } //get arguments for excel() if((pager = getenv("PAGER")) == NULL) pager = DEF_PAGER; if((argv0 = strrchr(pager, '/')) != NULL) argv0 ++; else argv0 = pager; if(execl(pager, argv0, (char *)0) < 0) err_sys("excel error for %s", pager); } exit(0); } </span>
2. popen函数和pclose函数
//创建一个管道,fork一个子进程,关闭未使用的管道段,执行一个shell运行命令,然后等待命令终止
FILE *popen(const char *cmdstring, const char* type);
int pclose(FILE* fp);
//将1中的第二个程序重新写
<span style="font-size:18px;">#include "apue.h" #include <sys/wait.h> #define PAGER "${PAGER:-more}" int main(int argc, char *argv[]) { char line[MAXLINE]; FILE *fpin, *fpout; if(argc != 2) err_quit("usage: a.out <pathname>"); if((fpin = fopen(argv[1], "r")) == NULL) err_sys("can't open %s", argv[1]); if((fpout = popen(PAGER, "w")) == NULL) err_sys("popen error"); while(fgets(line, MAXLINE, fpin) != NULL){ if(fputs(line, fpout) == EOF) err_sys("fputs error to pipe"); } if(ferror(fpin)) err_sys("fgets error"); if(pclose(fpout) == -1) err_sys("pclose error"); exit(0); } </span>
popen和pclose函数
<span style="font-size:18px;">#include "apue.h" #include <errno.h> #include <fcntl.h> #include <sys/wait.h> static pid_t *childpid = NULL; static int maxfd; FILE *popen(const char *cmdstring, const char *type) { int i; int pfd[2]; pid_t pid; FILE *fp; // only allow 'r' or 'w' if((type[0] != 'r' && type[0] != 'w') || type[1] != 0){ errno = EINVAL; return NULL; } //first time through if(childpid == NULL){ maxfd = open_max(); if((childpid = calloc(maxfd, sizeof(pid_t))) == NULL) return NULL; } if(pipe(pfd) < 0) return NULL; if(pfd[0] >= maxfd || pfd[1] >= maxfd){ close(pfd[0]); close(pfd[1]); errno = EMFILE; return NULL; } if((pid = fork()) < 0){ return NULL; } else if(pid == 0){ if(*type == 'r'){ close(pfd[0]); if(pfd[1] != STDOUT_FILENO){ dup2(pfd[1], STDOUT_FILENO); close(pfd[1]); } } else{ close(pfd[1]); if(pfd[0] != STDIN_FILENO){ dup2(pfd[0], STDIN_FILENO); close(pfd[0]); } } for(i = 0; i < maxfd; i ++) if(childpid[i] > 0) close(i); excel("/bin/sh", "sh", "-c", cmdstring, ((char *)0)); _exit(127); } else{ if(*type == 'r'){ close(pfd[1]); if((fp = fdopen(pfd[0], type)) == NULL) return NULL; } else{ close(pfd[0]); if((fp = fdopen(pfd[1], type)) == NULL) return NULL; } childpid[fileno(fp)] = pid; return fp; } } int pclose(FILE *fp) { int fd, stat; pid_t pid; if(childpid == NULL){ errno = EINVAL; return -1; } fd = fileno(fp); if(fd >= maxfd){ errno = EINVAL; return -1; } if((pid = childpid[fd]) == 0){ errno = EINVAL; return -1; } childpid[fd] = 0; if(fclose(fp) == EOF) return -1; while(waitpid(pid, &stat, 0) < 0) if(errno != EINTR) return -1; return stat; } </span>
//一个应用程序,它向标准输出写一个提示,然后从标准输入读1行。使用popen,可以在应用程序和输入之间插入一个程序一遍对输入进行变换控制
<span style="font-size:18px;">#include "apue.h" #include <ctype.h> int main(void) { int c; while((c = getchar()) != EOF){ if(isupper(c)) c = tolower(c); if(putchar(c) == EOF) err_sys("output error"); if(c == '\n') fflush(stdout); } exit(0); } #include "apue.h" #include <sys/wait.h> int main(void) { char line[MAXLINE]; FILE *fpin; if((fpin = popen("./myuclc", "r")) == NULL) err_sys("popen error"); for( ; ; ){ fputs("promt> ", stdout); fflush(stdout); if(fgets(line, MAXLINE, fpin) == NULL) break; if(fputs(line, stdout) == EOF) err_sys("fputs error to pipe"); } if(pclose(fpin) == -1) err_sys("pclose error"); putchar('\n'); exit(0); } /************** promt> FSDF fsdf promt> fsdafA fsdafa promt> ^C **************/ </span>
3. 协同进程
//当一个过滤程序既产生某个过滤程序的输入,又读取该过滤程序的输出时,它就变成了协同进程
//一个简单的协同进程,它从标准输入读取两个数,计算它们的和,然后将和写至其标准输出
<span style="font-size:18px;">#include "apue.h" int main(void) { int n, int1, int2; char line[MAXLINE]; while((n = read(STDIN_FILENO, line, MAXLINE)) > 0){ line = 0; if(sscanf(line, "%d%d", &int1, &int2) == 2){ sprintf(line, "%d\n", int1 + int2); n = strlen(line); if(write(STDOUT_FILENO, line, n) != n) err_sys("write error"); } else{ if(write(STDOUT_FILENO, "invalid args\n", 13) != 13) err_sys("write error"); } } exit(0); } #include "apue.h" static void sig_pipe(int); int main(void) { int n, fd1[2], fd2[2]; pid_t pid; char line[MAXLINE]; if(signal(SIGPIPE, sig_pipe) == SIG_ERR) err_sys("signal error"); if(pipe(fd1) < 0 || pipe(fd2) < 0) err_sys("pipe error"); if((pid = fork()) < 0){ err_sys("fork error"); } else if(pid > 0){ close(fd1[0]); close(fd2[1]); while(fgets(line, MAXLINE, stdin) != NULL){ n = strlen(line); if(write(fd1[1], line, n) != n) err_sys("write error to pipe"); if((n = read(fd2[0], line, MAXLINE)) < 0) err_sys("read error from pipe"); if(n == 0){ err_msg("child close pipe"); break; } line = 0; if(fputs(line, stdout) == EOF) err_sys("fputs error"); } if(ferror(stdin)) err_sys("fgets error on stdin"); exit(0); } else{ close(fd1[1]); close(fd2[0]); if(fd1[0] != STDIN_FILENO){ if(dup2(fd1[0], STDIN_FILENO) != STDIN_FILENO) err_sys("dup2 error to stdin"); close(fd1[0]); } if(fd2[1] != STDOUT_FILENO){ if(dup2(fd2[1], STDOUT_FILENO) != STDOUT_FILENO) err_sys("dup2 error to stdin"); close(fd2[1]); } if(execl("./add2", "add2", (char *)0) < 0) err_sys("excel error"); } exit(0); } static void sig_pipe(int signo) { fprintf(stdout, "SIGPIPE caught\n"); exit(1); } /************* 12 345 357 ad fd invalid args 123a46 invalid args 44 invalid args ^C **************/ </span>
4. FIFO(命名管道)
未命名管道只能在两个相关的进程之间,而且这两个的进程还要有一个共同创建了它们的祖先进程;但是,通过FIFO,不相关的进程也能交换数据
int mkfifo(const char *path, mode_t mode);
int mkfifoat(int fd, const char *path, mode_t mode);
用途:
i. shell命令使用FIFO将数据从一条管道传送到另一条时,无需创建中间临时文件;
ii. 客户进程-服务器进程应用程序中,FIFO用作汇聚点,在客户进程和服务进程二者之间传递数据。
5. XSI的IPC
有三种称作XSI的IPC的IPC: 消息队列,信号量,以及共享存储器
i. 标识符和键
标识符是IPC对象的内部名。为使多个合作进程能够在同一IPC对象上汇聚,需要提供一个外部命名方案。为此,每个IPC对象都与一个键相关联,将这个键作为该对象的外部名。
ii. 权限结构
iii. 结构限制
6. 消息队列
int msgget(key_t key, int flag); //打开一个现有队列或创建一个新队列
int msgctl(int msqid, int cmd, struct msqid_ds *buf); //根据cmd的不同,对队列进行多种操作
int msgsnd(int msqid, const void *ptr, size_t nbytes, int flag); //将数据放入消息队列中
ssize_t msgrcv(int msqid, void *ptr, size_t nbytes, long type, int flag); //从队列中取消息
//type == 0,返回队列中的第一个消息;type > 0,返回队列中消息类型为type的第一个消息;type < 0,返回队列中消息类型值小于等于type绝对值的消息。
7. 信号量
它是一个计数器,用于为多个进程提供对共享数据对象的访问。
int semget(key_t key, int nsems, int flag); //获得一个信号量ID
int semctl(int semid, int semnum, int cmd, .../* union semum arg */); //根据cmd的不同,执行不同的操作
int semop(int semid, struct sembuf semoparray[], size_t nops); //自动执行信号量集合上的操作数组
8. 共享存储
共享存储允许两个或多个进程共享一个给定的存储区。因为数据不需要在客户进程和服务器进程之间复制,所以这是最快的一种IPC。
int shmget(key_t key, size_t size, int flag); //获得一个共享存储标识符
int shmctl(int shmid, int cmd, struct shmid_ds *buf); //根据cmd的不同,进行不同的操作
void *shmat(int shmid, const void *addr, int flag); //一旦创建了一个共享存储段,将其连接到它的地址空间中
//addr为0 ,则此段连接到内核选择的第一个可用地址上;若非0,则连接到指定地址上
int shmdt(const void *arr); //当对共享存储段的操作已经结束时,与该段分离
//看共享存储段所存放的位置
<span style="font-size:18px;">#include "apue.h" #include <sys/shm.h> #define ARRAY_SIZE 40000 #define MALLOC_SIZE 100000 #define SHM_SIZE 100000 #define SHM_MODE 0600 char array[ARRAY_SIZE]; int main(void) { int shmid; char *ptr, *shmptr; printf("array[] from %p to %p\n", (void *)array, (void *)(array + ARRAY_SIZE)); printf("stack around %p\n", (void *)(&shmid)); if((ptr = malloc(MALLOC_SIZE)) == NULL) err_sys("malloc error"); printf("malloced from %p to %p\n", (void *)ptr, (void *)(ptr + MALLOC_SIZE)); if((shmid = shmget(IPC_PRIVATE, SHM_SIZE, SHM_MODE)) < 0) err_sys("shmget error"); if((shmptr = shmat(shmid, 0, 0)) == (void *)-1) err_sys("shmat error"); printf("shared memory attached from %p to %p\n", (void *)shmptr, (void *)(shmptr + SHM_SIZE)); if(shmctl(shmid, IPC_RMID, 0) < 0) err_sys("shmctl error"); exit(0); } /********************************************************** array[] from 0x6020e0 to 0x60bd20 stack around 0x7ffe3e84dbcc malloced from 0x1602010 to 0x161a6b0 shared memory attached from 0x7fe18bc66000 to 0x7fe18bc7e6a0 ***********************************************************/</span>
/dev/zero的存储映射
<span style="font-size:18px;">#include "apue.h" #include <fcntl.h> #include <sys/mman.h> #define NLOOPS 1000 #define SIZE sizeof(long) static int update(long *ptr) { return ((*ptr) ++); } int main(void) { int fd, i, counter; pid_t pid; void *area; if((fd = open("/dev/zero", O_RDWR)) < 0) err_sys("open error"); if((area = mmap(0, SIZE, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0)) == MAP_FAILED) err_sys("mmap error"); close(fd); TELL_WAIT(); if((pid = fork()) < 0){ err_sys("fork error"); } else if(pid > 0){ for(i = 0; i < NLOOPS; i += 2){ if((counter = update((long *)area)) != i) err_quit("parent: expected %d, got %d", i, counter); TELL_CHILD(pid); WAIT_CHILD(); } } else{ for(i = 1; i < NLOOPS; i += 2){ WAIT_PARENT(); if((counter = update((long *)area)) != i) err_quit("child: expected %d, got %d", i, counter); TELL_PARENT(getppid()); } } exit(0); } </span>
匿名存储映射(根据上面的程序进行修改)
i. 删除了/dev/zero的open语句; ii. 删除了fd的close语句; iii. 将mmap调用修改:
if((area = mmap(0, SIZE, PROT_READ | PROT_WRITE, MAP_ANON | MAP_SHARED, -1, 0)) == MAP_FAILED)
9. POSIX信号量
有两种形式:命名的和未命名的。 未命名的信号量只存在于内存中,并要求使用信号量的进程必须访问内存。命名信号量可以通过名字访问,因此可以被任何已知它们名字的进程中的线程使用。
//创建一个新的命名信号量或者使用一个现有信号量
sem_t *sem_open(const char *name, int oflag, ... /* mode_t mode, unsigned int value */);
int sem_close(sem_t *sem); //释放任何信号量相关的资源
int sem_unlink(const char *name); //销毁一个命名信号量
int sem_trywait(sem_t *sem); int sem_wait(sem_t *sem); //实现信号量减1操作
int sem_timedwait(sem_t *restrict sem, const struct timespec *restrict tsptr);
int sem_post(sem_t *sem); //使信号量值增1
int sem_init(sem_t *sem, int pshared, unsigned int value); //创建一个未命名的信号量
int sem_destroy(sem_t *sem); //未命名的信号量的使用完成时,对其丢弃
int sem_getvalue(sem_t *restrict sem, int *restrict valp); //检索信号量值
基于信号量的互斥原语的实现
<span style="font-size:18px;">#include "slock.h" #include <stdlib.h> #include <stdio.h> #include <unistd.h> #include <errno.h> struct slock* s_alloc() { struct slock *sp; static int cnt; if((sp = malloc(sizeof(struct slock))) == NULL) return NULL; do{ snprintf(sp->name, sizeof(sp->name), "%ld.%d", (long)getpid(), cnt++); sp->semp = sem_open(sp->name, O_CREAT | O_EXCL, S_IRWXU, 1); }while(sp->semp == SEM_FAILED && errno == EEIXST); if(sp->semp == SEM_FAILED){ free(sp); return NULL; } sem_unlink(sp->name); return sp; } void s_free(struct slock *sp) { sem_close(sp->semp); free(sp); } int s_lock(struct slock *sp) { return (sem_wait(sp->semp)); } int s_trylock(struct slock *sp) { return (sem_trywait(sp->semp)); } int s_unlock(struct slock *sp) { return (sem_post(sp->semp)); } </span>
10. 客户进程-服务器进程属性
相关文章推荐
- BAT大公司里怎样开发和部署web前端代码
- css设置鼠标放到图片上图片放大效果
- bfs/广度优先搜索实例
- app里面打开safari浏览器带入部分信息
- Oracle sql 去除重复 和 字段去除空格
- 【BZOJ】3436 小K的农场
- Ionic 常见问题及解决方案
- OC和Swift混编的一点小坑
- 纯java JJIL脸部识别人像识别
- 如何导出SAP的数据表字段和字段描述
- XEN - Installing and Configuring Fedora Xen Virtualization
- POJ 2785 Values whose Sum is 0
- 只读事务(@Transactional(readOnly = true))的一些概念
- CAAnimation动画全解析附2个Demo
- 能够提高开发效率的Eclipse实用操作
- linux ssh 使用深度解析【key登录详解】
- Lucky String
- 119. Pascal's Triangle II
- fl2400开发板rt3070芯片—station模式
- Linux内核入门简介