2018-09-14 12:22:41
Gaodes
码龄5年
关注
管道的概念
管道是Unix中最古老的进程间通信的形式。 我们把从一个进程连接到另一个进程的一个数据流称为一个“管道” 我们通常把是把一个进程的输出连接或“管接”(经过管道来连接)到另一个进程的输入。
管道特点
管道是半双工的,数据只能向一个方向流动;需要双方通信时,需要建立起两个管道 只能用于父子进程或者兄弟进程之间(具有亲缘关系的进程)进行通信;通常,一个管道由一个进程创建,然后该进程调用fork,此后父、子进程之间就可应用该管道。
pipe函数
包含头文件<unistd.h>功能:创建一无名管道 原型
int pipe(int file_descriptor[2])
参数 file_descriptor:文件描述符数组,其中file_descriptor[0]表示读端,file_descriptor[1]表示写端 返回值:成功返回0,失败返回错误代码
示例代码:
#include<stdio.h>
#include<unistd.h>
#include<stdlib.h>
#include<signal.h>
#include<string.h>
int main(int argc,char *argv[])
{
int fd[2]
printf("f[0]=%d,f[1]=%d\n",fd[0],fd[1])
pipe(fd)
printf("f[0]=%d,f[1]=%d\n",fd[0],fd[1])
char buf[1024]={0}
int fid = fork()
if(fid >0)
{
read(fd[0],buf,1024)
printf("read data %s\n",buf)
}
else if(fid == 0)
{
write(fd[1],"helloworld",strlen("helloworld"))
}
else
{
perror("fork error")
}
return 0
}
打印结果
管道读写规则:如果试图从管道写端读取数据,或者向管道读端写入数据都将导致错误发生 当没有数据可读时,read调用就会阻塞,即进程暂停执行,一直等到有数据来到为止。 如果管道的另一端已经被关闭,也就是没有进程打开这个管道并向它写数据时,read调用就会阻塞
复制文件描述符dup
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<signal.h>
int main()
{
int fd = dup(1)
printf("file fd= %d\n",fd)
write(fd,"helloworld",strlen("helloworld"))
return 0
}
打印结果:
1为输入到终端
shell管道的实现
原理通过把发的fd[1]写复制到shell的1(标准输入),fd[0]复制到shell的2(标准输出)
以下是代码:
#include<stdio.h>
#include<stdlib.h>
#include<fcntl.h>
#include<unistd.h>
#include<string.h>
#include<signal.h>
int main()
{
int fd[2]
char buf[1024] ={0}
pipe(fd)
int pid = fork()
if(pid >0)
{
read(fd[0],buf,1024)
printf(buf)
}
else if(pid == 0)
{
dup2(fd[1],1)
close(fd[0])
close(fd[1])
execlp("ls","ls","-al",NULL)
}
else
{
}
return 0
}
实现结果:
popen函数
作用:允许一个程序把另外一个程序当作一个新的进程来启 动,并能对它发送数据或接收数据
FILE* popen(const char *command,const char *open_mode)
command:待运行程序的名字和相应的参数 open_mode:必须是“r”或“w” 如果 *** 作失败,popen会返回一个空指针
以下代码:
#include<stdio.h>
#include<stdlib.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<fcntl.h>
#include<string.h>
int main()
{
FILE *file = popen("ls -al","r")
char buf[1024] = {0}
fread(buf,1,1024,file)
fclose(file)
FILE *wcfile = popen("wc","w")
fwrite(buf,1,strlen(buf),wcfile)
fclose(wcfile)
return 0
}
代码结果:
命名管道破裂测试
我们首先要知道命名管道,要读段和写段同时开启,才能向文件读写数据。
贴上代码来理解命名管道的规则
首先是读端:
#include<stdio.h>
#include<unistd.h>
#include<stdlib.h>
#include<signal.h>
#include<string.h>
#include<fcntl.h>
int main(int argc,char *argv[])
{
printf("open before\n")
int fd = open("/home/gao/tmp/fifo",O_RDONLY)
printf("open after\n")
//休眠5秒,读端退出
sleep(5)
return 0
}
接下来是写端:
#include<stdio.h>
#include<unistd.h>
#include<stdlib.h>
#include<signal.h>
#include<string.h>
#include<fcntl.h>
void handle(int signo)
{
printf("cat signale = %d\n",signo)
}
int main(int argc,char *argv[])
{
signal(SIGPIPE,handle)
printf("open before\n")
int fd = open("/home/gao/tmp/fifo",O_WRONLY)
printf("open after\n")
//命名管道规则,如果写入读断被中断,写入会返回-1,并且管道会破裂,产生信号(SIGPIPE)
while(1)
{
int wrsize = write(fd,"helloworld",strlen("helloworld"))
printf("size data:%d\n",wrsize)
sleep(1)
}
}
执行写端:
它在等待另一端的开启,才能向里面写入数据
此时我们开启读端:
马上可以看到写段可以写数据
而执行5秒后,我们可以看到写的时候返回-1,并且获取到管道破裂的信息(SIGPIPE)
所以这里就是我们所注意的点,当我们写客户端和服务器进行管道传输的时候,如果客户端一旦退出来,就会使管道破裂,所以我们必须通过捕捉信号,来避免这种事情发生。
iOS SDK中提供了一个现成的函数 NSSetUncaughtExceptionHandler用来做异常处理,但功能非常有限,而引起崩溃的大多数原因如:内存访问错误,重复释放等错误就无能为力了,因为这种错误它抛出的是Signal,所以必须
要专门做Signal处理。首先定义一个UncaughtExceptionHandler类,.h头文件的代码如下:
#import <UIKit/UIKit.h>@interface UncaughtExceptionHandler : NSObject { BOOL dismissed} @end void InstallUncaughtExceptionHandler()然后在.mm文件实现InstallUncaughtExceptionHandler(),如下: void InstallUncaughtExceptionHandler() { signal(SIGABRT, MySignalHandler)signal(SIGILL, MySignalHandler)signal(SIGSEGV, MySignalHandler)signal(SIGFPE, MySignalHandler)signal(SIGBUS, MySignalHandler)signal(SIGPIPE, MySignalHandler)}
这样,当应用发生错误而产生上述Signal后,就将会进入自己自定义的回调函数MySignalHandler。为了得到崩溃时的现场信息,还可以加入一些获取CallTrace及设备信息的代码,.mm文件的完整代码如下:
#import "UncaughtExceptionHandler.h" #include <libkern/OSAtomic.h>#include <execinfo.h>NSString * const UncaughtExceptionHandlerSignalExceptionName = @"UncaughtExceptionHandlerSignalExceptionName"NSString * const UncaughtExceptionHandlerSignalKey = @"UncaughtExceptionHandlerSignalKey"NSString * const UncaughtExceptionHandlerAddressesKey = @"UncaughtExceptionHandlerAddressesKey"volatile int32_t UncaughtExceptionCount = 0const int32_t UncaughtExceptionMaximum = 10const NSInteger UncaughtExceptionHandlerSkipAddressCount = 4const NSInteger UncaughtExceptionHandlerReportAddressCount = 5@implementation UncaughtExceptionHandler + (NSArray *)backtrace { void* callstack[128]int frames = backtrace(callstack, 128)char **strs = backtrace_symbols(callstack, frames) int iNSMutableArray *backtrace = [NSMutableArray arrayWithCapacity:frames]for ( i = UncaughtExceptionHandlerSkipAddressCounti <UncaughtExceptionHandlerSkipAddressCount + UncaughtExceptionHandlerReportAddressCounti++) { [backtrace addObject:[NSString stringWithUTF8String:strs[i]]]} free(strs) return backtrace} - (void)alertView:(UIAlertView *)anAlertView clickedButtonAtIndex:(NSInteger)anIndex { if (anIndex == 0) { dismissed = YES} } - (void)handleException:(NSException *)exception { UIAlertView *alert = [[[UIAlertView alloc] initWithTitle:NSLocalizedString(@"Unhandled exception", nil) message:[NSString stringWithFormat:NSLocalizedString( @"You can try to continue but the application may be unstable.\n" @"%@\n%@", nil), [exception reason], [[exception userInfo] objectForKey:UncaughtExceptionHandlerAddressesKey]] delegate:self cancelButtonTitle:NSLocalizedString(@"Quit", nil) otherButtonTitles:NSLocalizedString(@"Continue", nil), nil] autorelease][alert show] CFRunLoopRef runLoop = CFRunLoopGetCurrent()CFArrayRef allModes = CFRunLoopCopyAllModes(runLoop) while (!dismissed) { for (NSString *mode in (NSArray *)allModes) { CFRunLoopRunInMode((CFStringRef)mode, 0.001, false)} }CFRelease(allModes)NSSetUncaughtExceptionHandler(NULL)signal(SIGABRT, SIG_DFL)signal(SIGILL, SIG_DFL)signal(SIGSEGV, SIG_DFL)signal(SIGFPE, SIG_DFL)signal(SIGBUS, SIG_DFL)signal(SIGPIPE, SIG_DFL) if ([[exception name] isEqual:UncaughtExceptionHandlerSignalExceptionName]) { kill(getpid(), [[[exception userInfo] objectForKey:UncaughtExceptionHandlerSignalKey] intValue])} else { [exception raise]} } @end NSString* getAppInfo() { NSString *appInfo = [NSString stringWithFormat:@"App : %@ %@(%@)\nDevice : %@\nOS Version : %@ %@\nUDID : %@\n", [[NSBundle mainBundle] objectForInfoDictionaryKey:@"CFBundleDisplayName"], [[NSBundle mainBundle] objectForInfoDictionaryKey:@"CFBundleShortVersionString"], [[NSBundle mainBundle] objectForInfoDictionaryKey:@"CFBundleVersion"], [UIDevice currentDevice].model, [UIDevice currentDevice].systemName, [UIDevice currentDevice].systemVersion, [UIDevice currentDevice].uniqueIdentifier]NSLog(@"Crash!!!! %@", appInfo)return appInfo} void MySignalHandler(int signal) { int32_t exceptionCount = OSAtomicIncrement32(&UncaughtExceptionCount)if (exceptionCount >UncaughtExceptionMaximum) { return} NSMutableDictionary *userInfo = [NSMutableDictionary dictionaryWithObject:[NSNumber numberWithInt:signal] forKey:UncaughtExceptionHandlerSignalKey]NSArray *callStack = [UncaughtExceptionHandler backtrace][userInfo setObject:callStack forKey:UncaughtExceptionHandlerAddressesKey] [[[[UncaughtExceptionHandler alloc] init] autorelease] performSelectorOnMainThread:@selector(handleException:) withObject: [NSException exceptionWithName:UncaughtExceptionHandlerSignalExceptionName reason: [NSString stringWithFormat: NSLocalizedString(@"Signal %d was raised.\n" @"%@", nil), signal, getAppInfo()] userInfo: [NSDictionary dictionaryWithObject:[NSNumber numberWithInt:signal] forKey:UncaughtExceptionHandlerSignalKey]] waitUntilDone:YES]} void InstallUncaughtExceptionHandler() { signal(SIGABRT, MySignalHandler)signal(SIGILL, MySignalHandler)signal(SIGSEGV, MySignalHandler)signal(SIGFPE, MySignalHandler)signal(SIGBUS, MySignalHandler)signal(SIGPIPE, MySignalHandler)}
在应用自身的 didFinishLaunchingWithOptions 前,加入一个函数:
- (void)installUncaughtExceptionHandler { InstallUncaughtExceptionHandler()}
最后,在
didFinishLaunchingWithOptions 中加入这一句代码就行了:
[self InstallUncaughtExceptionHandler]
现在,基本上所有崩溃都能Hold住了。
实验三 进程间通信UNIX/LINUX系统的进程间通信机构(IPC)允许在任意进程间大批量地交换数据。本实验的目的是了解和熟悉LINUX支持的信号量机制、管道机制、消息通信机制及共享存储区机制。
(一) 信号机制实验
实验目的
1、了解什么是信号
2、熟悉LINUX系统中进程之间软中断通信的基本原理
实验内容
1、编写程序:用fork( )创建两个子进程,再用系统调用signal( )让父进程捕捉键盘上来的中断信号(即按^c键);捕捉到中断信号后,父进程用系统调用kill( )向两个子进程发出信号,子进程捕捉到信号后分别输出下列信息后终止:
Child process1 is killed by parent!
Child process2 is killed by parent!
父进程等待两个子进程终止后,输出如下的信息后终止:
Parent process is killed!
2、分析利用软中断通信实现进程同步的机理
实验指导
一、信号
1、信号的基本概念
每个信号都对应一个正整数常量(称为signal number,即信号编号。定义在系统头文件<signal.h>中),代表同一用户的诸进程之间传送事先约定的信息的类型,用于通知某进程发生了某异常事件。每个进程在运行时,都要通过信号机制来检查是否有信号到达。若有,便中断正在执行的程序,转向与该信号相对应的处理程序,以完成对该事件的处理;处理结束后再返回到原来的断点继续执行。实质上,信号机制是对中断机制的一种模拟,故在早期的UNIX版本中又把它称为软中断。
信号与中断的相似点:
(1)采用了相同的异步通信方式;
(2)当检测出有信号或中断请求时,都暂停正在执行的程序而转去执行相应的处理程序;
(3)都在处理完毕后返回到原来的断点;
(4)对信号或中断都可进行屏蔽。
信号与中断的区别:
(1)中断有优先级,而信号没有优先级,所有的信号都是平等的;
(2)信号处理程序是在用户态下运行的,而中断处理程序是在核心态下运行;
(3)中断响应是及时的,而信号响应通常都有较大的时间延迟。
信号机制具有以下三方面的功能:
(1)发送信号。发送信号的程序用系统调用kill( )实现;
(2)预置对信号的处理方式。接收信号的程序用signal( )来实现对处理方式的预置;
(3)收受信号的进程按事先的规定完成对相应事件的处理。
2、信号的发送
信号的发送,是指由发送进程把信号送到指定进程的信号域的某一位上。如果目标进程正在一个可被中断的优先级上睡眠,核心便将它唤醒,发送进程就此结束。一个进程可能在其信号域中有多个位被置位,代表有多种类型的信号到达,但对于一类信号,进程却只能记住其中的某一个。
进程用kill( )向一个进程或一组进程发送一个信号。
3、对信号的处理
当一个进程要进入或退出一个低优先级睡眠状态时,或一个进程即将从核心态返回用户态时,核心都要检查该进程是否已收到软中断。当进程处于核心态时,即使收到软中断也不予理睬;只有当它返回到用户态后,才处理软中断信号。对软中断信号的处理分三种情况进行:
(1)如果进程收到的软中断是一个已决定要忽略的信号(function=1),进程不做任何处理便立即返回;
(2)进程收到软中断后便退出(function=0);
(3)执行用户设置的软中断处理程序。
二、所涉及的中断调用
1、kill( )
系统调用格式
int kill(pid,sig)
参数定义
int pid,sig
其中,pid是一个或一组进程的标识符,参数sig是要发送的软中断信号。
(1)pid>0时,核心将信号发送给进程pid。
(2)pid=0时,核心将信号发送给与发送进程同组的所有进程。
(3)pid=-1时,核心将信号发送给所有用户标识符真正等于发送进程的有效用户标识号的进程。
2、signal( )
预置对信号的处理方式,允许调用进程控制软中断信号。
系统调用格式
signal(sig,function)
头文件为
#include <signal.h>
参数定义
signal(sig,function)
int sig
void (*func) ( )
其中sig用于指定信号的类型,sig为0则表示没有收到任何信号,余者如下表:
值 名 字 说 明
01 SIGHUP 挂起(hangup)
02 SIGINT 中断,当用户从键盘按^c键或^break键时
03 SIGQUIT 退出,当用户从键盘按quit键时
04 SIGILL 非法指令
05 SIGTRAP 跟踪陷阱(trace trap),启动进程,跟踪代码的执行
06 SIGIOT IOT指令
07 SIGEMT EMT指令
08 SIGFPE 浮点运算溢出
09 SIGKILL 杀死、终止进程
10 SIGBUS 总线错误
11 SIGSEGV 段违例(segmentation violation),进程试图去访问其虚地址空间以外的位置
12 SIGSYS 系统调用中参数错,如系统调用号非法
13 SIGPIPE 向某个非读管道中写入数据
14 SIGALRM 闹钟。当某进程希望在某时间后接收信号时发此信号
15 SIGTERM 软件终止(software termination)
16 SIGUSR1 用户自定义信号1
17 SIGUSR2 用户自定义信号2
18 SIGCLD 某个子进程死
19 SIGPWR 电源故障
function:在该进程中的一个函数地址,在核心返回用户态时,它以软中断信号的序号作为参数调用该函数,对除了信号SIGKILL,SIGTRAP和SIGPWR以外的信号,核心自动地重新设置软中断信号处理程序的值为SIG_DFL,一个进程不能捕获SIGKILL信号。
function 的解释如下:
(1)function=1时,进程对sig类信号不予理睬,亦即屏蔽了该类信号;
(2)function=0时,缺省值,进程在收到sig信号后应终止自己;
(3)function为非0,非1类整数时,function的值即作为信号处理程序的指针。
三、参考程序
#include <stdio.h>
#include <signal.h>
#include <unistd.h>
void waiting( ),stop( )
int wait_mark
main( )
{
int p1,p2,stdout
while((p1=fork( ))= =-1) /*创建子进程p1*/
if (p1>0)
{
while((p2=fork( ))= =-1)/*创建子进程p2*/
if(p2>0)
{
wait_mark=1
signal(SIGINT,stop) /*接收到^c信号,转stop*/
waiting( )
kill(p1,16) /*向p1发软中断信号16*/
kill(p2,17) /*向p2发软中断信号17*/
wait(0) /*同步*/
wait(0)
printf("Parent process is killed!\n")
exit(0)
}
else
{
wait_mark=1
signal(17,stop) /*接收到软中断信号17,转stop*/
waiting( )
lockf(stdout,1,0)
printf("Child process 2 is killed by parent!\n")
lockf(stdout,0,0)
exit(0)
}
}
else
{
wait_mark=1
signal(16,stop) /*接收到软中断信号16,转stop*/
waiting( )
lockf(stdout,1,0)
printf("Child process 1 is killed by parent!\n")
lockf(stdout,0,0)
exit(0)
}
}
void waiting( )
{
while(wait_mark!=0)
}
void stop( )
{
wait_mark=0
}
四、运行结果
屏幕上无反应,按下^C后,显示 Parent process is killed!
五、分析原因
上述程序中,signal( )都放在一段程序的前面部位,而不是在其他接收信号处。这是因为signal( )的执行只是为进程指定信号值16或17的作用,以及分配相应的与stop( )过程链接的指针。因而,signal( )函数必须在程序前面部分执行。
本方法通信效率低,当通信数据量较大时一般不用此法。
六、思考
1、该程序段前面部分用了两个wait(0),它们起什么作用?
2、该程序段中每个进程退出时都用了语句exit(0),为什么?
3、为何预期的结果并未显示出?
4、程序该如何修改才能得到正确结果?
5、不修改程序如何得到期望的输出?
欢迎分享,转载请注明来源:内存溢出
评论列表(0条)