进程间通信-信号-pipe-fifo

题目
编译运行附件中的代码,提交运行结果截图
理解代码,特别是相关系统调用的使用。

管道PIPE
知识点
1.概念
进程间通信的一般目的,大概有数据传输、共享数据、通知事件、资源共享和进程控制等。但是对于每一个进程来说这个进程看到属于它的一块内存资源,这块资源是它所独占的,所以进程之间的通信就会比较麻烦,原理就是需要让不同的进程间能够看到一份公共的资源。所以交换数据必须通过内核,在内核中开辟一块缓冲区,进程1把数据从用户空间 拷到内核缓冲区,进程2再从内核缓冲区把数据读走,内核提供的这种机制称为进程间通信。
管道是一种最基本的进程间通信机制。
2.管道的创建
通过man -k pipe | grep 2查找相关函数,在确定创建管道的函数即为pipe,函数原型如下:

调用pipe函数,会在内核中开辟出一块缓冲区用来进行进程间通信,这块缓冲区称为管道,它有一个读端和一个写端。
pipe函数接受一个参数,是包含两个整数的数组,如果调用成功,会通过pipefd[2]传出给用户程序两个文件描述符,需要注意pipefd [0]指向管道的读端, pipefd [1]指向管道的写端,那么此时这个管道对于用户程序就是一个文件,可以通过read(pipefd [0]);或者write(pipefd [1])进行操作。pipe函数调用成功返回0,否则返回-1。

父进程创建管道,得到两个文件描述符指向管道的两端

利用fork函数创建出子进程,则子进程也得到两个文件描述符指向同一管道。

父进程关闭读端(pipe[0]),子进程关闭写端pipe[1],则此时父进程可以往管道中进行写操作,子进程可以从管道中读,从而实现了通过管道的进程间通信。

实例
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);
}

pipedemo.c

#include    <stdio.h>
#include    <stdlib.h>
#include    <string.h>
#include    <unistd.h>

int main()
{
	int	len, i, apipe[2];//两个文件描述符	
	char	buf[BUFSIZ];//长度为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) ){//从输入端获取字符,存入buf数组中
		len = strlen( buf );
		if (  write( apipe[1], buf, len) != len ){//apipe[1]是写入端,这里write()函数将buf指针指向的内存的len长个字节写入到apipe[1]所指向的管道缓冲区中。
			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;					
		}
	}
}

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 );
}

testtty.c

#include <unistd.h>

int main()
{
	char *buf = "abcde\n";
	write(0, buf, 6);
}

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;
}

有名管道FIFO
知识点
1.概念
管道中,只有具有血缘关系的进程才能进行通信,对于后来的命名管道,就解决了这个问题。FIFO不同于管道之处在于它提供一个路径名与之关联,以FIFO的文件形式存储于文件系统中。命名管道是一个设备文件,因此,即使进程与创建FIFO的进程不存在亲缘关系,只要可以访问该路径,就能够通过FIFO相互通信。值得注意的是, FIFO(first input first output)总是按照先进先出的原则工作,第一个被写入的数据将首先从管道中读出。
2.有名管道的创建
通过man -k pipe | grep named命令查找有名管道相关信息如下:

再搜索图中函数得知创建命名管道的系统函数 :mkfifo,定义在头文件sys/stat.h,明其存取权限; dev为设备值,该值取决于文件创建的种类,它只在创建设备文件时才会用到。这两个函数调用成功都返回0,失败都返回-1。下面使用mkfifo函数创建了一个命名管道:

umask(0);
   if (mkfifo("/tmp/fifo",S_IFIFO|0666) == -1)
   {
       perror("mkfifo error!");
        exit(1);
   }

"S_IFIFO|0666"指明创建一个命名管道且存取权限为0666,即创建者、与创建者同组的用户、其他用户对该命名管道的访问权限都是可读可写( 这里要注意umask对生成的管道文件权限的影响 。命名管道创建后就可以使用了,命名管道和管道的使用方法法基本是相同的。只是使用命名管道时,必须先调用open()将其打开。因为命名管道是一个存在于硬盘上的文件,而管道是存在于内存中的特殊文件。

需要注意的是,调用open()打开命名管道的进程可能会被阻塞。但如果同时用读写方式( O_RDWR)打开,则一定不会导致阻塞;如果以只读方式( O_RDONLY)打开,则调用open()函数的进程将会被阻塞直到有写方打开管道;同样以写方式( O_WRONLY)打开也会阻塞直到有读方式打开管道。

实例
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);
}

以“0777”文件创建模式(即-rwx rwx rwx)创建管道。则:
(1)该文件拥有者对该文件拥有读写操作的权限
(2)该文件拥有者所在组的其他成员对该文件拥有读写操作的权限
(3)其他用户组的成员对该文件也拥有读写操作权限

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);
}

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);
}


消费者端读取出了当时在生产者端写入的20201319,fifo管道建立成功。

signal
知识点
1.概念
每个进程收到的所有信号,都是有内核负责发送的,内核处理。可以在控制台输入kill -l命令查看Linux支持的信号:

2.发送信号函数
(1)发送信号
int kill(pid_t pid, int sig);

pid取值 作用
pid > 0 指定进程
pid = 0 与调用kill函数进程数与同组的所有进程
pid < -1 取pid绝对值对应的进程组
pid = -1 发送给进程有权限发送的系统中所有进程

(2) 定时器
unsigned int alarm(unsigned int seconds);
每个进程有唯一一个定时器,设置时间,返回的是上一次设置定时的剩余时间值,时间值被新值代替,到时内核发送SIGALRM给当前进程。SIGALRM的默认动作是终止进程,如果相捕捉该信号可以在alarm之前注册信号捕捉函数。

alarm(0);	//取消闹钟。
int getitimer(int which, struct itimerval *curr_value);
int setitimer(int which, const struct itimerval *new_value, 
			  struct itimerval *old_value);	// 精度更高us
which设置定时模式:
	ITIMER_REAL:自然计时			->	SIGALRM
	ITIMER_VIRTUAL:用户空间计时	    ->	SIGVTALRM
	ITIMER_PROF:内核 + 用户空间计时	->	SIGPROF
	
struct itimerval {
	struct timeval it_interval; /* Interval for periodic timer */
	struct timeval it_value;    /* Time until next expiration */
};

struct timeval {
	time_t      tv_sec;         /* seconds */
	suseconds_t tv_usec;        /* microseconds */
};

(3) 其他发送信号函数
int raise(int sig);
给自己发信号:raise(signo); 等价于 kill(getpid(), signo);

void abort(void);
调用raise(SIGABRT);使程序异常终止,可以在信号处理函数里面做清理工作,但是如果在信号处理函数里面不退出进程,调用结束后也会结束进程,并且向主机环境发送一个异常终止的通知。

int pause(void);
调用该函数会使该进程挂起直至捕捉到一个信号。执行完信号处理函数pause才返回-1并且设置error为EINTER。

(4)注册信号处理函数signal
typedef void (*sighandler_t)(int);
sighandler_t signal(int signum, sighandler_t handler);

实例
sitactdemo.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);
}

输入信号(其所对应的数据为 x),打印Called with signal x,等待 4* x 秒,打印done handling signal x。

sitactdemo2.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;
}

每隔两秒打印Two seconds passed

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");
}

每隔两秒打印一个hello,如果输入Ctrl+C,立刻打印OUCH。

sigdemo.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");
	}
}

持续每隔1秒打印haha。

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");
}

当输入一般数据时,将其输出;
当输入Ctrl+C,输出 Received signal 2 .. waiting ,等待2秒后打印Leaving inthandler;
当输入Ctrl+,输出 Received signal 3 .. waiting ,等待3秒后打印Leaving quithandler。

posted @ 2022-11-13 19:43  20201319吴向林  阅读(52)  评论(0编辑  收藏  举报