进程间通信-信号-pipe-fifo
一、FIFO有名管道
(一)知识点归纳
- 
FIFO也称为有名管道,FIFO不同于管道之处在于它提供一个路径名与之关联。有名管道也被称为FIFO文件,是一种特殊的文件。由于linux所有的事物都可以被视为文件,所以对命名管道的使用也就变得与文件操作非常统一。其打开方式与打开一个普通文件是一样的,这样即使与 FIFO 的创建进程不存在亲缘关系的进程,只要可以访问该路径,就能够彼此通过 FIFO 相互通信,因此,通过 FIFO 不相关的进程也能交换数据。 
- 
一旦打开了 FIFO,就能在它上面使用与操作匿名管道和其他文件的系统调用一样的 I/O 系统调用了(如read()、write() 和 close())。与管道一样,FIFO 也有一个写入端和读取端,并且从管道中读取数据的顺序与写入的顺序是一样的。FIFO 的名称也由此而来:先入先出。 
(二)使用方法
- 
通过命令创建有名管道:mkfifo name 
- 
通过函数创建有名管道 

使用man 3 mkfifo查看函数详情:

#include <sys/types.h>
#include <sys/stat.h>
int mkfifo(const char *pathname, mode_t mode);
pathname:管道名称的路径
mode:文件的权限
返回值:成功返回0,失败返回-1。
(三)读管道
- 
管道中有数据:read 返回实际读到的字节数 
- 
管道中无数据: 
- 
- 管道写端被全部关闭,read 返回 0(相当与读到文件末尾)。
 
- 
- 写端没有全部被关闭:read 阻塞等待
 
(四)写管道
- 
管道读端被全部关闭,进程异常终止(收到一个 SIGPIPE 信号) 
- 
管道读端没有全部关闭: 
- 
- 管道已经满了,write 会阻塞
 
- 
- 管道没有满,write 将数据写入,并返回实际写入的字节数
 
(五)代码
- testmf.c
#include  <stdio.h>
#include  <stdlib.h>
#include  <sys/types.h>
#include  <sys/stat.h>
int main()
{
	int res = mkfifo("/tmp/myfifo", 0777);
	if (res == 0) {
		printf("FIFO created \n");
	}
	exit(EXIT_SUCCESS);
}
2.producer.c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <fcntl.h>
#include <limits.h>
#include <sys/types.h>
#include <sys/stat.h>
#define FIFO_NAME "/tmp/myfifo"
#define BUFFER_SIZE PIPE_BUF
#define TEN_MEG (1024 * 1024 * 10)
int main()
{
	int pipe_fd;
	int res;
	int open_mode = O_WRONLY;
	int bytes = 0;
	char buffer[BUFFER_SIZE + 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());
	pipe_fd = open(FIFO_NAME, open_mode);
	printf("Process %d result %d\n", getpid(), pipe_fd);
	if (pipe_fd != -1) {
		while (bytes < TEN_MEG) {
			res = write(pipe_fd, buffer, BUFFER_SIZE);
			if (res == -1) {
				fprintf(stderr, "Write error on pipe\n");
				exit(EXIT_FAILURE);
			}
			bytes += res;
		}
		close(pipe_fd);
	} else {
		exit(EXIT_FAILURE);
	}
	printf("Process %d finish\n", getpid());
	exit(EXIT_SUCCESS);
}
- consumer.c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <fcntl.h>
#include <limits.h>
#include <sys/types.h>
#include <sys/stat.h>
#define FIFO_NAME "/tmp/myfifo"
#define BUFFER_SIZE PIPE_BUF
int main()
{
	int pipe_fd;
	int res;
	int open_mode = O_RDONLY;
	char buffer[BUFFER_SIZE + 1];
	int bytes = 0;
	memset(buffer, 0, sizeof(buffer));
	printf("Process %d opeining FIFO O_RDONLY \n", getpid());
	pipe_fd = open(FIFO_NAME, open_mode);
	printf("Process %d result %d\n", getpid(), pipe_fd);
	if (pipe_fd != -1) {
		do {
			res = read(pipe_fd, buffer, BUFFER_SIZE);
			bytes += res;
		} while (res > 0);
		close(pipe_fd);
	} else {
		exit(EXIT_FAILURE);
	}
	printf("Process %d finished, %d bytes read\n", getpid(), bytes);
	exit(EXIT_SUCCESS);
}
(六)运行结果


二、管道(PIPE)
(一)知识归纳
- 
不同进程间的通信:进程之间可以看到一份公共资源;而提供这份资源的形式或者提供者不同,造成了通信方式不同,而 pipe就是提供这份公共资源的形式的一种。其本质是一个伪文件(实为内核缓冲区),由两个文件描述符引用,一个表示读端,一个表示写端。 
- 
局限性 
- 
数据自己读不能自己写。 
- 
数据一旦被读走,便不在管道中存在,不可反复读取。 
- 
由于管道采用半双工通信方式。因此,数据只能在一个方向上流动。 
- 
只能在有公共祖先的进程间使用管道。 
常见的通信方式有,单工通信、半双工通信、全双工通信。
(二)创建管道
- 通过指令 man -k pipe | grep create 寻找所需函数


#include <unistd.h>
int pipe (int fd[2]);
                         //返回:成功返回0,出错返回-1 
- 创建过程
(1)父进程创建管道,得到两个件描述符指向管道的两端
(2)父进程fork出子进程,子进程也有两个文件描述符指向同管道。
(3)父进程关闭fd[0],子进程关闭fd[1],即子进程关闭管道读端,父进程关闭管道写端(因为管道只支持单向通信)。子进程可以往管道中写,父进程可以从管道中读,管道是由环形队列实现的,数据从写端流入从读端流出,这样就实现了进程间通信。

(三)标准流管道
标准流管道将一系列的创建过程合并到一个函数popen()中完成。它所完成的工作有以下几步。
- 创建一个管道
- fork()一个子进程
- 在父子进程中关闭不需要的文件描述符
- 执行exec函数族调用
- 执行函数中所指定的命令

(四)代码
- listargs.c
#include<stdio.h>
main( int ac, char *av[] )
{
	int	i;
	printf("Number of args: %d, Args are:\n", ac);
	for(i=0;i<ac;i++)
		printf("args[%d] %s\n", i, av[i]);
	fprintf(stderr,"This message is sent to stderr.\n");
}

- pipe.c
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#define	oops(m,x)	{ perror(m); exit(x); }
int main(int ac, char **av)
{
	int	thepipe[2],
		newfd,
		pid;
	if ( ac != 3 ){
		fprintf(stderr, "usage: pipe cmd1 cmd2\n");
		exit(1);
	}
	if ( pipe( thepipe ) == -1 )
		oops("Cannot get a pipe", 1);
	if ( (pid = fork()) == -1 )
		oops("Cannot fork", 2);
	if ( pid > 0 ){
		close(thepipe[1]);
		if ( dup2(thepipe[0], 0) == -1 )
			oops("could not redirect stdin",3);
		close(thepipe[0]);
		execlp( av[2], av[2], NULL);
		oops(av[2], 4);
	}
	close(thepipe[0]);
	if ( dup2(thepipe[1], 1) == -1 )
		oops("could not redirect stdout", 4);
	close(thepipe[1]);
	execlp( av[1], av[1], NULL);
	oops(av[1], 5);
}
3.pipedemo.c
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<unistd.h>
int main()
{
	int	len, i, apipe[2];
	char	buf[BUFSIZ];
	if ( pipe ( apipe ) == -1 ){
		perror("could not make pipe");
		exit(1);
	}
	printf("Got a pipe! It is file descriptors: { %d %d }\n",
							apipe[0], apipe[1]);
	while ( fgets(buf, BUFSIZ, stdin) ){
		len = strlen( buf );
		if (  write( apipe[1], buf, len) != len ){
			perror("writing to pipe");
			break;
		}
		for ( i = 0 ; i<len ; i++ )
			buf[i] = 'X' ;
		len = read( apipe[0], buf, BUFSIZ ) ;
		if ( len == -1 ){
			perror("reading from pipe");
			break;
		}
		if ( write( 1 , buf, len ) != len ){
			perror("writing to stdout");
			break;
		}
	}
}

4.pipedemo2.c
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<unistd.h>
#define	CHILD_MESS	"I want a cookie\n"
#define	PAR_MESS	"testing..\n"
#define	oops(m,x)	{ perror(m); exit(x); }
main()
{
	int	pipefd[2];
	int	len;
	char	buf[BUFSIZ];
	int	read_len;
	if ( pipe( pipefd ) == -1 )
		oops("cannot get a pipe", 1);
	switch( fork() ){
		case -1:
			oops("cannot fork", 2);
		case 0:
			len = strlen(CHILD_MESS);
			while ( 1 ){
				if (write( pipefd[1], CHILD_MESS, len) != len )
					oops("write", 3);
				sleep(5);
			}
		default:
			len = strlen( PAR_MESS );
			while ( 1 ){
				if ( write( pipefd[1], PAR_MESS, len)!=len )
					oops("write", 4);
				sleep(1);
				read_len = read( pipefd[0], buf, BUFSIZ );
				if ( read_len <= 0 )
					break;
				write( 1 , buf, read_len );
			}
	}
}

- stdinredir1.c
#include<stdio.h>
#include<fcntl.h>
int main()
{
	int	fd ;
	char	line[100];
	fgets( line, 100, stdin ); printf("%s", line );
	fgets( line, 100, stdin ); printf("%s", line );
	fgets( line, 100, stdin ); printf("%s", line );
	close(0);
	fd = open("/etc/passwd", O_RDONLY);
	if ( fd != 0 ){
		fprintf(stderr,"Could not open data as fd 0\n");
		exit(1);
	}
	fgets( line, 100, stdin ); printf("%s", line );
	fgets( line, 100, stdin ); printf("%s", line );
	fgets( line, 100, stdin ); printf("%s", line );
}

- stdinredir2.c
#include<stdio.h>
#include<stdlib.h>
#include<fcntl.h>
//#define	CLOSE_DUP
//#define	USE_DUP2
main()
{
	int	fd ;
	int	newfd;
	char	line[100];
	fgets( line, 100, stdin ); printf("%s", line );
	fgets( line, 100, stdin ); printf("%s", line );
	fgets( line, 100, stdin ); printf("%s", line );
	fd = open("data", O_RDONLY);
#ifdef CLOSE_DUP
	close(0);
	newfd = dup(fd);
#else
	newfd = dup2(fd,0);
#endif
	if ( newfd != 0 ){
		fprintf(stderr,"Could not duplicate fd to 0\n");
		exit(1);
	}
	close(fd);
	fgets( line, 100, stdin ); printf("%s", line );
	fgets( line, 100, stdin ); printf("%s", line );
	fgets( line, 100, stdin ); printf("%s", line );
}

- whotofile.c
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
int main()
{
	int	pid ;
	int	fd;
	printf("About to run who into a file\n");
	if( (pid = fork() ) == -1 ){
		perror("fork"); exit(1);
	}
	if ( pid == 0 ){
		close(1);				/* close, */
		fd = creat( "userlist", 0644 );		/* then open */
		execlp( "who", "who", NULL );		/* and run	*/
		perror("execlp");
		exit(1);
	}
	if ( pid != 0 ){
		wait(NULL);
		printf("Done running who.  results in userlist\n");
	}
	return 0;
}

- testtty.c
#include <unistd.h>
int main()
{
	char *buf = "abcde\n";
	write(0, buf, 6);
}

三、signal
(一)信号基本概念
信号是一条小的消息,由内核或者其它进程生成并发送至目标进程,目标进程可以根据该信号来做出响应。信号可以由进程或者内核发出,例如:
- 
用户在Bash界面通过键盘对正在执行的进程输入Ctrl+C、Ctrl+\等信号命令,或者执行kill命令发送信号。 
- 
进程执行出错,例如访问了一个非法的地址、除0运算,或者硬件发生故障,就会由内核向进程发送一个信号。 
- 
进程执行kill命令向目标进程发送信号。 
(二)信号发送步骤
- 
发送信号:内核通过更新目标进程上下文的某个状态,传递一个信号给目标进程。 
- 
接收信号:目标进程会被内核强制以某种方式对信号的发送做出反应,它就会接收到信号。如果程序没有针对这种信号指定其处理方式,就会采用默认的处理策略,例如中止进程、忽略。 
(三)信号种类
每种信号对应于一种系统事件,Linux提供以下几种基本的信号种类:

[root@bogon ~]# kill -l
- SIGHUP 2) SIGINT 3) SIGQUIT 4) SIGILL 5) SIGTRAP
- SIGABRT 7) SIGBUS 8) SIGFPE 9) SIGKILL 10) SIGUSR1
- SIGSEGV 12) SIGUSR2 13) SIGPIPE 14) SIGALRM 15) SIGTERM
- SIGSTKFLT 17) SIGCHLD 18) SIGCONT 19) SIGSTOP 20) SIGTSTP
- SIGTTIN 22) SIGTTOU 23) SIGURG 24) SIGXCPU 25) SIGXFSZ
- SIGVTALRM 27) SIGPROF 28) SIGWINCH 29) SIGIO 30) SIGPWR
- SIGSYS 34) SIGRTMIN 35) SIGRTMIN+1 36) SIGRTMIN+2 37) SIGRTMIN+3
- SIGRTMIN+4 39) SIGRTMIN+5 40) SIGRTMIN+6 41) SIGRTMIN+7 42) SIGRTMIN+8
- SIGRTMIN+9 44) SIGRTMIN+10 45) SIGRTMIN+11 46) SIGRTMIN+12 47) SIGRTMIN+13
- SIGRTMIN+14 49) SIGRTMIN+15 50) SIGRTMAX-14 51) SIGRTMAX-13 52) SIGRTMAX-12
- SIGRTMAX-11 54) SIGRTMAX-10 55) SIGRTMAX-9 56) SIGRTMAX-8 57) SIGRTMAX-7
- SIGRTMAX-6 59) SIGRTMAX-5 60) SIGRTMAX-4 61) SIGRTMAX-3 62) SIGRTMAX-2
- SIGRTMAX-1 64) SIGRTMAX
- 每个信号类型都有一个预定义的默认行为:
- 进程终止
- 进程终止并且将进程上下文dump到硬盘
- 进程挂起直到被SIGCONT信号唤醒
- 进程接收该信号,不进行任何处理。
(四)代码
1.sigactdemo.c
#include<stdio.h>
#include<unistd.h>
#include<signal.h>
#define	INPUTLEN	100
void inthandler();
int main()
{
	struct sigaction newhandler;
	sigset_t blocked;
	char x[INPUTLEN];
	newhandler.sa_handler = inthandler;
	newhandler.sa_flags = SA_RESTART|SA_NODEFER
		|SA_RESETHAND;
	sigemptyset(&blocked);
	sigaddset(&blocked, SIGQUIT);
	newhandler.sa_mask = blocked;
	if (sigaction(SIGINT, &newhandler, NULL) == -1)
		perror("sigaction");
	else
		while (1) {
			fgets(x, INPUTLEN, stdin);
			printf("input: %s", x);
		}
	return 0;
}
void inthandler(int s)
{
	printf("Called with signal %d\n", s);
	sleep(s * 4);
	printf("done handling signal %d\n", s);
}

2.sigactdemo2.c
#include <unistd.h>
#include <signal.h>
#include <stdio.h>
void sig_alrm( int signo )
{
	/*do nothing*/
}
unsigned int mysleep(unsigned int nsecs)
{
	struct sigaction newact, oldact;
	unsigned int unslept;
	newact.sa_handler = sig_alrm;
	sigemptyset( &newact.sa_mask );
	newact.sa_flags = 0;
	sigaction( SIGALRM, &newact, &oldact );
	alarm( nsecs );
	pause();
	unslept = alarm ( 0 );
	sigaction( SIGALRM, &oldact, NULL );
	return unslept;
}
int main( void )
{
	while( 1 )
	{
		mysleep( 2 );
		printf( "Two seconds passed\n" );
	}
	return 0;
}

3.sigdemo1.c
#include<stdio.h>
#include<signal.h>
void	f(int);
int main()
{
	int	i;
	signal( SIGINT, f );
	for(i=0; i<5; i++ ){
		printf("hello\n");
		sleep(2);
	}
	return 0;
}
void f(int signum)
{
	printf("OUCH!\n");
}

4.sigdemo2.c
#include<stdio.h>
#include<signal.h>
main()
{
	signal( SIGINT, SIG_IGN );
	printf("you can't stop me!\n");
	while( 1 )
	{
		sleep(1);
		printf("haha\n");
	}
}

5.sigdemo3.c
#include<stdio.h>
#include<string.h>
#include<signal.h>
#include<unistd.h>
#define	INPUTLEN	100
int main(int argc, char *argv[])
{
	void inthandler(int);
	void quithandler(int);
	char input[INPUTLEN];
	int nchars;
	signal(SIGINT, inthandler);//^C
	signal(SIGQUIT, quithandler);//^\
	do {
		printf("\nType a message\n");
		nchars = read(0, input, (INPUTLEN - 1));
		if (nchars == -1)
			perror("read returned an error");
		else {
			input[nchars] = '\0';
			printf("You typed: %s", input);
		}
	}
	while (strncmp(input, "quit", 4) != 0);
	return 0;
}
void inthandler(int s)
{
	printf(" Received signal %d .. waiting\n", s);
	sleep(2);
	printf("  Leaving inthandler \n");
}
void quithandler(int s)
{
	printf(" Received signal %d .. waiting\n", s);
	sleep(3);
	printf("  Leaving quithandler \n");
}


 
                
            
         浙公网安备 33010602011771号
浙公网安备 33010602011771号