在linux系统中,有多个进程同时存在,进程之间需要通信,进程间通信(IPC)有以下方法:
管道
信号
消息队列
信号量
共享内存
1.管道
管道是用来进行进程间通信的一块内核缓冲区,它按照先进先出的方式进行数据传输。管道的两端都是进程,进程从一端往管道里写入,
一端读出,
管道的两端一般使用文件描述符fd[0]和fd[1]来表示,两端的任务是固定的。读用fd[0],写用fd[1].
管道可以分为匿名管道和命名管道两种,匿名管道比命名管道占用更少的系统资源,但其功能不如命名管道强大。
1.匿名管道
匿名管道只能用于有亲缘关系的进程,如父子进程,兄弟进程,
1.匿名管道的创建
使用pipe函数可以创建一个匿名管道
int pipe(int fd[2]);
2.匿名管道的读写
read和write函数
2.命名管道
与匿名管道不同,命名管道在文件系统中是可见的,创建时需要指定具体的路径和文件名,创建后可以使用ls命令来查看。
1.命名管道的创建
创建命名管道与创建普通文件类似
int mkfifo(const char * pathname,mode_t mode);
2.命名管道的删除
删除指定的命名管道可以使用unlink函数,
3.命名管道的打开
与匿名管道不同,在对命名管道进行读写操作之前,需要调用open函数
4.命名管道的读写
从命名管道中读取数据时,对于设置了阻塞标志的读操作,如果当前命名管道中没有数据,而且没有进程为写入数据而打开
该命名管道时,read函数一直阻塞,直到有新的数据写入。
向命名管道中写入数据时,对于设置了阻塞标志位的写操作,如果要写入的字节数不大于PIPE_BUF,linux会保证写入的原子性。
大于PIPE_BUF,write会阻塞。
2.信号
信号用于通知一个或多个接受进程有某种事件发生,除了进程间通信外,还可以发送信号给进程本身。
1.信号的基本原理
信号是linux系统中唯一的异步通信机制,也可以看做是异步通知,通知接收信号的进程有某种事件发生,这类似于DOS下的INIT或
windows下的事件,由于信号是异步的,进程不必等待信号的到达,事实上,它也不知道信号会何时到达。
信号一般是由系统中一些特定事件引起的,主要包括如下:
硬件故障;
程序运行中的错误,例如除数为0,或访问进程以外的内存区域。
进程的子进程终止;
用户从终端向进程发送终止、终止等信号。
进程调用kill、raise,以及alarm等函数向其他进程发送信号。
进程收到信号后,对于某些特定的信号,例如SIGKILL和SIGSTOP信号,处理方式是确定的。对于大部分,进程可以选择不同的
响应方式:
捕获信号:这类似于中断处理程序,对于需要需要处理的信号,进程可以指定相应的函数来进行处理。
忽略信号:对信号不进行处理,就像未收到一样,有两个信号不能忽略,即SIGKILL和SIGSTOP信号。
让LINUX内核执行与信号对应的默认动作,对于大部分信号来说,默认的处理方式是终止相应的进程。
进程可以通过signal和sigaction函数来选择具体的响应方式。
2.信号的类型
可以使用kill -l查看系统中的所有信号
1-31为unix继承过来的,
信号::
SIGUP:用户终端关闭时发出,通知同一会话中的所有进程组,包括前台进程组,包括前台进程组和后台对终端有输出
的进程组,这些进程收到SIGHUP信号之后,默认的处理方式是终止进程。也可以选择忽略该信号。
SIGINT:进程终止信号,一般情况下,用户在键盘上按下CTRL+C时发出,该信号会被传送给前台进程组中的每一个进程。
SIGQUIT:与SIGINT信号类似,一般情况下是用户在键盘上按下CTRL+\时发出,在进程终止时会产生一个core文件。
SIGILL:非法的硬件指令,应用程序通常需要捕捉该信号来响应运行过程中出现的错误,
SIGTRAP:用于程序调试,由断点产生
SIGABRT:调用abort函数时生成的信号。
SIGBUS:进程使用错误的内存地址时产生该信号,
SIGFPE:程序运行时发生了致命的算术运算错误,
SIGKILL:进程终止信号,可以立即停止,而且不能被忽略、阻塞或处理
SIGUSR1:用户自定义信号,在应用程序中使用。
SIGSEGV:进程使用非法内存地址时产生该信号。
SIGUSR2:用户自定义信号,在应用程序中使用
SIGPIPE:向一个读进程已经终止的管道中写入数据时,会产生该信号。
SIGALRM:时钟信号,可以使用alarm函数来设定该信号
SIGTERM:时钟终止信号,调用kill函数时产生这个信号。
SIGSTKFLT:协处理器堆栈错误。
SIGCHLD:子进程结束时,父进程就会收到这个信号,一般情况下,该信号的处理函数都会调用wait函数来获取子进程
的进程号和终止状态。
SIGCONT:使一个被中断的进程继续运行。
SIGSTOP:暂停进程运行,该信号不能被忽略、阻塞或处理
SIGTTIN:当后台运行的进程要从控制终端读取数据时产生该信号
SIGTTOU:当后台运行的进程要向控制终端写入数据时产生该信号
SIGURG:通知内核有要求立即处理的socket
SIGXCPU:进程超过系统设置的CPU时间限制
SIGXFSZ:进程超过系统设定的文件大小限制
SIGVTALRM:虚拟时钟信号,类似于SIGALRM,计算进程执行的时间
SIGPROF:与SIGALRM和SIGVTALRM信号类似,包括进程执行的时间和系统调用的时间
SIGWINCH:X Windows窗口改变大小时产生这个信号
SIGIO:可以进行I/O操作
SIGPWR:电源故障
SIGSYS:非法的系统调用
3.信号的处理函数
1.signal函数
定义:
void (*signal(int signum,void(* handler)(int)))(int);
上面函数可以简化为:
typedef void (*sighandler_t)(int);
sighandler_t signal(int sig, sighandler_t handler);
函数中,参数sig为要设置处理函数的信号,参数handler为指向函数的指针,用来设定信号的处理函数,也可以
使下面的某个值:
SIG_IGN:忽略参数sig所指定的信号;
SIG_DFL:采用系统默认方式处理sig所指定信号。
2.sigaction函数
该函数与signal函数类似,只是它支持信号带有参数,进而可以传递信息给处理函数。
定义:
int sigaction(int sig,const struct sigaction *act ,struct sigaction *oldact);
参数sig为信号,参数act用来设定信号的处理函数,如果为NULL,则系统默认处理,参数oldact用来保存
信号以前的处理信息,一般设置为NULL
4.信号发送函数
linux系统中最常用的信号发送函数主要有:kill\ raise\ alarm以及 setitimer等。
1.kill函数
kill函数用于向进程或进程组发送一个信号:
定义:
int kill(pid_t pid,int sig);
表头文件:
#include<sys/types.h>
#include<signal.h>
该函数根据参数pid的具体取值,
pid>0:将信号sig发送给进程标示符为pid的进程
pid=0:将信号sig发送给当前进程所在进程组中的所有进程
pid=-1:将信号sig发送给除init进程和当前进程以外的所有进程
pid<-1:将信号sig发送给进程组-pid中的所有进程
如果sig为0,则不发送任何信号。kill函数执行成功后,返回值为0.执行过程中遇到错误时返回-1,并设置相应
的错误码。
EINVAL:参数sig指定信号无效,
ESRCH:参数pid指定的进程或进程组不存在
EPERM:进程没有权限将信号发送给指定的接收进程。
2.raise函数
用于向进程本身发送信号
int raise(int sig)
3.abort函数
abort函数用于向进程发送SIGABORT信号,默认情况下进程会异常退出
4.sigqueue函数
用于向进程发送信号,同时还支持附加信息的传递
int sigqueue(pid_t pid, int sig, const union sigval val)
参数pid为是接收进程的标示符,参数sig为要发送的信号值,参数val为一个共享体,指定信号的附加信息。
接收用sigaction函数
5.alarm函数
用于在系统中设置一个定时器,计时到达后向进程发送SIGALRM信号。
6.setitimer和getitimer函数
setitimer函数用来设置定时器,getitimer函数用来读取定时器的状态,比alarm函数的功能更为强大。
int setitimer(int whitch, struct itimerval *value)
int getitimer(int whitch, const struct itimerval *value, struct itimerval *ovalue)
参数whitch为定时器类型:
ITIMER_REAL:按实际时间计时,计时到达后发送SIGALRM信号
ITIMER_VIRTUAL:仅在进程执行时计时,计时到达后向进程发送SIGVTALRM信号
ITIMER_PROF:在进程执行和调用系统时计时,计时到达后向进程发送SIGPROF信号
5.信号集和信号集操作函数
如果在程序运行过程中不希望收到其他信号,这时就需要进行信号屏蔽,
信号集是用来表示多个信号的集合,
linux系统中的所有信号都可以出现在信号集中,对信号集进行操作主要有以下几个函数:
定义:
int sigemptyset(sigset_t *set);
表头文件:
#include<signal.h>
说明:
sigemptyset()用来将参数set信号集初始化并清空。
定义:
int sigfillset(sigset_t * set);
表头文件:
#include<signal.h>
说明:
sigfillset()用来将参数set信号集初始化, 然后把所有的信号加入到此信号集里。
返回值:
执行成功则返回0, 如果有错误则返回-1。
定义:
int sigaddset(sigset_t *set,int signum);
表头文件:
#include<signal.h>
说明:
sigaddset()用来将参数signum 代表的信号加入至参数set 信号集里。
返回值:
执行成功则返回0, 如果有错误则返回-1。
定义:
int sigdelset(sigset_t * set,int signum);
表头文件:
#include<signal.h>
说明:
sigdelset()用来将参数signum代表的信号从参数set信号集里删除。
返回值:
执行成功则返回0, 如果有错误则返回-1。
定义:
int sigismember(const sigset_t *set,int signum);
表头文件:
#include<signal.h>
说明:
sigismember()用来测试参数signum 代表的信号是否已加入至参数set信号集里。如果信号集里已有该信号则返回1, 否则返回0。
返回值:
信号集已有该信号则返回1, 没有则返回0。如果有错误则返回-1。
sigprocmask函数用来设置进程的信号屏蔽码,使进程在某段时间内阻塞信号集中的信号
定义:
int sigprocmask(int how,const sigset_t *set,sigset_t * oldset);
表头文件:
#include<signal.h>
说明:
sigprocmask()可以用来改变目前的信号遮罩, 其操作依参数how来决定
SIG_BLOCK 新的信号遮罩由目前的信号遮罩和参数set 指定的信号遮罩作联集
SIG_UNBLOCK 将目前的信号遮罩删除掉参数set指定的信号遮罩
SIG_SETMASK 将目前的信号遮罩设成参数set指定的信号遮罩。
如果参数oldset不是NULL指针, 那么目前的信号遮罩会由此指针返回。
返回值:
执行成功则返回0, 如果有错误则返回-1。
3.消息队列
消息队列是一种比较高级的进程间通信方法,能够将格式化的数据单元传送给任意的进程,它与命名管道十分类似。
1.消息队列的创建
msgget函数用来创建或打开一个消息队列
为了创建一个新的消息队列,或存取一个已经存在的队列,要使用msgget()系统调用。
原型:
int msgget ( key_t key, int msgflg );
返回: 成功,则返回消息队列识别号,失败,则返回-1,
semget()中的第一个参数是键, 这个键值要与现有的键值进行比较,现有的键值指在内核中已存在的其它
消息队列的键值。对消息队列的打开或存取操作依赖于msgflg参数的取值:
IPC_CREAT : 如果这个队列在内核中不存在,则创建它。
IPC_EXCL :当与IPC_CREAT一起使用时,如果这个队列已存在,则创建失败。
如果IPC_CREAT单独使用,semget()为一个新创建的消息队列返回标识号,或者返回具有相同键值的已存
在队列的标识号。如果IPC_EXCL与IPC_CREAT一起使用,要么创建一个新的队列,要么对已存在的队列返回-1。
IPC_EXCL单独是没有用的,当与IPC_CREAT结合起来使用时,可以保证新创建队列的打开和存取。
key_t也可以调用ftok函数来生成,
2.消息队列的控制
msgctl函数用来对消息队列进行各种操作,例如修改消息队列的属性、认清队列中的所有消息。
3.消息队列的读写
msgsnd函数用来向消息队列写入一个消息,
如果msgsnd函数被阻塞,则在下面某一个条件满足时解除阻塞
1)消息队列中有容纳要写入消息的空间
2)消息队列被删除
3)进程被信号中断
msgrcv函数用来从消息队列读取一个消息
4.信号量
信号量(Semaphore),也称为信号灯,主要来控制对个进程对共享资源
1.信号量的创建
semget函数用来创建或打开一个信号量集,
semget
其中信号量的键值可以由用户直接指定,也可以调用ftok函数来生成。
2.信号量的控制
semctl用来对信号量进行控制
3.信号量的操作
semop函数用来对信号量操作
5.共享内存
共享内存就是多个进程将同一块内存区域映射到自己的进程空间之中,他是进程间通信方式中最快的一种。
1.共享内存的创建
shmget函数用来创建或打开一块共享内存。
2.共享内存的读写
shmat函数用来访问
没有任何进程会再次使用某共享内存时,可以调用shmdt函数使其脱离进程的地址空间。