第十五章 IPC

1. 管道

  侷限性:歷史上,它們是半雙工;管道只能在具有公共祖先的兩個進程間使用。

  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[n] = 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[n] = 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. 客戶進程-服務器進程屬性

  








發佈了100 篇原創文章 · 獲贊 5 · 訪問量 3萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章