命名管道
管道是指进程直接进行交换数据的通道,可分为普通管道(pipe)和命名管道(FIFO),他们都是通过内核缓冲区按先进先出的方式进行数据传输,管道一端顺序地写入数据,另一端顺序地读入数据,管道最大的一个特点就是半双工性,数据只能向一个方向流动,如果需要进行双方的通信时,则需要建立两个管道。
管道和命名管道是最早的进程间的通信机制之一,其中管道用于相互关联的进程之间进行通信,比如父子进程。为了克服必须在具有亲缘关系的进程之间才能通信,于是产生了命名管道——即有名字的管道。
命名管道使得没有关系的进程之间也可以进行通信,命名管道提供一个与之关联的路径名,因此一个进程不需要与命名管道的创建具有相同的祖先,就可以通过该命名管道与之进行进程间的通信(只要可以访问该路劲名),实际上,命名管道就是利用建立于文件系统的特殊文件,以FIFO的文件形式存在于文件系统中,永久保存相关信息,,因此不同的进程可以打开命名管道进行读写,从而实现通信。
对于命名管道的操作与文件操作非常相似,对文件操作中使用的函数read(),write(),close()等函数都可以是用来对管道进行操作。
命名管道的创建:
#include<sys/types.h>
#include<sys/stat.h>
#include<fcntl.h>
int mkfifo(const char *pathname, mode_t mode);
//mode S_IRUSR S_IWUSR
//mode O_CREAT | O_EXCL
if (access(write_fifo_name,F_OK) == -1)
{
//mkfifo(write_fifo_name, S_IRUSR|S_IWUSR) == -1; //创建命名管道
}
该函数的第一个参数是一个普通的路径名,也就是创建后FIFO的名字,第二个参数与打开普通的open()函数中的mode参数相同,如果mkfifo的第一个参数是一个已经存在的路径名时,会返回EEXIST错误,所以一般典型的调用代码首先会检查是否返回该错误,如果确实返回该错误,那么只要调用打开FIFO的函数就可以了。
管道仅需要创建而不需要打开,因为使用它们的进程通过继承获得了管道的文件描述符,但命名管道则需要打开,因为使用它们的进程可以没有任何关系,对命名管道的打开通常使用文件打开函数open().
write_fd = open(write_fifo_name,O_WRONLY) == -1;
FIFO 常用的打开方式:
O_RDONLY:以只读并且阻塞的方式打开,如果已经有相应进程为写而打开该FIFO,则打开操作将成功返回,否则,可能阻塞直到有相应进程为写而打开该FIFO。
O_WRONLY:以只写并且阻塞的方式打开,如果已经有相应进程为读而打开该FIFO,则打开操作将成功返回,否则,可能阻塞直到有相应进程为读而打开该FIFO。
O_RDONLY|O_NONBLOCK:以只读并且非阻塞得方式打开,无论是否已经具有相应进程为写进程而打开该FIFO,均立即成功返回。
O_WRONLY|O_NONBLOCK:以只写并且非阻塞得方式打开,立即返回,如果已经有相应进程为读而打开该FIFO,则可以根据返回得文件描述符进行写操作,如果没有进程以只读方式打开,则返回错误-1.
如果以O_RDONLY模式打开FIFO,且已经有相应进程以写模式打开FIFO,那么打开操作将成功返回,否则,打开该FIFO的进程将可能被阻塞直到有相应进程以写模式打开FIFO(当前打开操作设置了阻塞标志(O_NONBKOCK))或成功返回(当前打开操作没有设置阻塞标志)。
如果以O_WRONLY模式打开FIFO;……
如果有进程以写模式打开了FIFO,但该FIFO中没有数据,那么读FIFO的进程将被阻塞(打开操作设置了阻塞标志),或返回-1(当前打开操作没有设置阻塞标志)
如果打开的FIFO设置了阻塞标志,对于读操作来说,造成阻塞的原因有两种:当前FIFO内有数据,但有其他进程在读这些数据,另外就是FIFO内没有数据,解除阻塞的则是FIFO中有新的数据写入(不管写入的数据量有多少,也不管读操作请求多少数据,都将解阻塞)
以读模式打开的FIFO的阻塞标志只对打开进程的第一个读操作有效,如果该进程有多个读操作序列,则在第一个读操作被唤醒并完成读操作后,其他将要执行的读操作将不再阻塞,即使在执行读操作时,FIFO中没有数据也一样不再阻塞(此时,读操作返回0)
如果没有进程以写模式打开FIFO,则设置了阻塞标志的读操作会阻塞。
如果FIFO中有数据,则设置了阻塞标志的读操作不会因为FIFO中的字节数小于请求读的字节数而阻塞,此时,读操作会返回FIFO中现有的数据量。
如果写打开时设置了阻塞标志,一个进程对FIFO的写操作可能被阻塞,当要写入FIFO的数据量 不大于PIPE_BUF时,Linux将保证写入的原子性,当FIFO的空闲缓冲区不足以容纳要写入的字节数时,写进程将被阻塞,直到缓冲区中能够容纳要写入的字节数时才被唤醒写入没有写入的数据字节,当要写入FIFO的数据量大于PIPE_BUF时,Linux将不再保证写入的原子性,FIFO缓冲区一有空闲区域,写进程就会试图向管道写入数据,写操作在写完所有请求写的数据后返回。
如果写打开时没有设置阻塞标志,当要写入FIFO的数据量大于PIPE_BUF时,Linux将不再保证写入原子性,在写满所有FIFO空闲缓冲区后,写操作返回,当要写入FIFO的数据量不大于PIPE_BUF时,Linux将保证写入的原子性,如果当前FIFO空想缓冲区能够容纳请求写入的字节数,写完成后成功返回,如果当前FIFO空闲缓冲区不能容纳请求写入的字节数,则返回错误。
信号
信号是由单个词组成的消息,比如指示灯是一个信号,眼神是一个信号,裁判手势也是一个信号,这些事物和事件本身不是消息,而他们的语意所表达的内容才是消息。在Linux中,经常我们按键时内核会向当前正在运行的进程发送一个中断信号,这个信号和一个数字编码相对应。
信号来源:
● 用户——用户通过输入Ctrl+C等按键,请求内核产生信号
● 内核——当内核执行出现错误时,内核给进程发送一个信号,
● 进程——一个进程可以通过系统调用kill给另外一个进程发送信号,同时,一个进程也可以和另外一个进程通过信号进行通信。
信号的产生:
信号大概的来源可以分为两类,一是硬件原因,一是软件原因。
最常用的4个发出信号系统函数是kill(),raise(),alarm(),setitimer()
其中 int kill(pid_t pid, int sig)中的pid说明为:
pid > 0 将信号传给进程号为PID的进程
pid = 0 将信号传给和目前进程相同进程组得所有进程
pid = -1 将信号广播传送给系统内所有得进程(不包括自己)
pid < 0 将信号传给进程号为pid绝对值得所有进程
信号操作:
操作信号最常用的方法就是信号屏蔽,信号屏蔽一般用到的函数为:
sigset_t
信号集及信号集操作函数:信号集被定义为一种数据类型:
typedef struct {
unsigned long sig[_NSIG_WORDS];
} sigset_t
#define _NSIG 64
#define _NSIG_BPW 32
#define _NSIG_WORDS (_NSIG / _NSIG_BPW)
信号集用来描述信号的集合,linux所支持的所有信号可以全部或部分的出现在信号集中,主要与信号阻塞相关函数配合使用。下面是为信号集操作定义的相关函数:
int sigemptyset(sigset_t *set);
int sigfillset(sigset_t *set);
int sigaddset(sigset_t *set, int signum)
int sigdelset(sigset_t *set, int signum);
int sigismember(const sigset_t *set, int signum);
int sigaction( int sig, const struct sigaction *act,struct sigaction *oact );
头文件
#include <signal.h>
sigemptyset(sigset_t *set)初始化由set指定的信号集,信号集里面的所有信号被清空;
sigfillset(sigset_t *set)调用该函数后,set指向的信号集中将包含linux支持的64种信号;
sigaddset(sigset_t *set, int signum)在set指向的信号集中加入signum信号;
sigdelset(sigset_t *set, int signum)在set指向的信号集中删除signum信号;
sigismember(const sigset_t *set, int signum)判定信号signum是否在set指向的信号集中。
int sigaction( int sig, const struct sigaction *act,struct sigaction *oact )检查、修改和指定信号相关联的信号响应。
#include<signal.h>.
Int sigaction(int signum, const struct sigaction *act, struct sigaction *oldact);
此函数是用来检测和改变信号行为且功能强大的一个函数。
Struct sigaction
{
Void (* sa_handler) (int);
Sigset_t sa_mask;
Int sa_flags;
Void (* sa_reatorer)(void);
}
信号量
信号量(Semaphore)是一个特殊得整数值,主要用来控制多个进程对临界资源的互斥访问,进程根据信号量来判断是否有可供访问得资源,要注意与信号得区别,信号是一种处理异步事件的方法,而信号量是一种进程同步机制,信号与信号量是两个不同得东西。
信号量是一个计数器,可用于同步多进程对共享数据对象得访问,为了获得共享资源,进程需要执行以下操作:
1、测试控制该资源的信号量
2、若此信号量的值为正,则进程可以使用该资源,进程将信号量值减1,表示它使用了一个资源单位
3、若此信号量的值为0,则进程进入睡眠状态,直至信号量值大于0。当进程被唤醒后,它返回至第1布。
当进程不再使用由一个信号量控制得共享资源时,该信号量值增1.如果有进程正在睡眠以等待此信号量,则唤醒他们,为了正确地实现信号量,信号量值得测试及减1操作应当是原子操作,为此,信号量通常是在内核中实现得。
常用的信号量一般初始值为1,只控制单个资源,有时也称互斥锁,但是,信号量得初值可以是任意一正值,该值说明有多少个共享资源单位可供共享应用,信号量有以下3个特性:
1、信号量并非是一个非负值,而必须将信号量定义为含有一个或多个信号量值得集合,当创建一个信号量时,要指定该集合中的各个值。
2、创建信号量对其赋初值分开,这是一个致命弱点,因为不能原子地创建一个信号量集合,并且对该集合中的所有值赋初值。
3、即使没有进程使用,但他们仍然存在,因此必须考虑在进程终止时有没有释放得信号量。
以上的三个特性就导致了信号使用的复杂性。