IPC主题¶
管道
命名管道/FIFO
信号
共享内存
内存映射文件
锁定共享内存
档案
域套接字
门
TCP/IP
IPC性能层次结构¶
- 最快-不需要内核交互,只需设置:
共享内存
锁定共享内存
- 速度非常快-涉及虚拟内存管理器
内存映射文件
- FAST-需要系统调用-即上下文切换
管道、FIFO/命名管道
信号
域套接字
- 中速/慢速-涉及FS或网络
档案
TCP/UDP套接字
管道¶
管道是除文件之外最古老的UNIXIPC机制。
管道是半双工的-数据只在一个方向上流动
管道只能在具有公共父级的进程之间使用。管道由父进程创建,然后通过fork()调用继承。
通过管道将数据从一个进程传递到另一个进程至少需要两次上下文切换。第三个是控制返回到写入过程所必需的。
一般来说,管道是非常快的。以100s到1000s的速率发送数据是可能的/S。
管道¶
- 管道有两个文件描述符
有一个文件描述符是只读的
一个文件描述符是只写的
- 创建管道的Linux调用如下:
内部管道(内部管道 [2] );
管道fd [0] 是只读的一半
管道fd [1] 是只写的那一半吗
- 创建管道的Windows调用为:
Bool CreateTube(HandleReHandle,Handle WriteHandle,LPSECURITY_Attributes attr,DWORD nSize);
ReadHandle和WriteHandle是读/写文件句柄
Attr允许在多个用户帐户下运行的进程管理各个管道的安全性
NSize是为管道建议的缓冲区大小
管道-Linux¶
1#include <stdio.h>
2#include <unistd.h>
3#include <fcntl.h>
4#include <stdlib.h>
5
6int main(int argc, char* argv[]) {
7
8 int pipes[2];
9 pipe(pipes);
10
11 int inputPartOfPipe = pipes[0];
12 int outputPartOfPipe = pipes[1];
13
14 int pid = fork();
15
16 if(pid > 0) { //parent process
17 dup2(inputPartOfPipe, 0); // redirect STDIN
18 close(outputPartOfPipe); // close unused half of pipe
19 int value;
20 scanf("%d\n", &value);
21 printf("child sent value = %d\n", value);
22 } else if(pid == 0) { //child process
23 dup2(outputPartOfPipe, 1); // redirect STDOUT
24 close(inputPartOfPipe); // close unused half of pipe
25 printf("%d\n", 5000);
26 } else {
27 printf("fork failed!\n");
28 }
29
30 //don't worry about closing remaining pipes,
31 //process exit does this for us
32
33 return 0;
34}
管道-上下文切换¶

命名管道/FIFO¶
命名管道与常规管道相同,但以下情况除外:
命名管道位于文件系统命名空间中
命名管道的寿命可能不同于单个进程
由于命名管道是文件,因此可以对其应用更高级的权限
在Linux中使用常规管道的示例¶
示例::
$ cat file | gzip -c9 > file.gz
Linux中命名管道的用法示例:
$ mkfifo pipe_file
$ gzip -c9 < pipe_file > file.gz
$ cat file > pipe_file
$ rm -f pipe_file
命名管道-常见用法¶
命名管道通常用于单机客户端/服务器应用程序
在Windows中,命名管道可以位于TCP/IP之上,并用于机器内的IPC。
- 以提高仅使用文件而不使用普通管道的应用程序的性能。
如果程序使用随机I/O,则不起作用
编写为从磁盘读取大文件的程序可以从命名管道读取。命名管道的数据可以由另一个程序生成。
通过命名管道和可将URL写入标准输出的命令,可以使只能使用本地磁盘中的文件的程序使用Web上的文件
此模式可用于将加密、文件压缩和其他扩展名添加到尚未安装它们的程序中。
命名管道-原子读/写¶
如果两个进程同时写入命名管道,会发生什么情况?
如果每次写入的大小(...)调用为<=PIPE_BUF,则写入将是原子的且按顺序进行。
如果每次写入的大小(...)Call is>PIPE_BUF,则单个写入将被分解并与其他并发调用方交织。
因此,一个很好的经验法则是确保写入命名管道的内容在大小上小于PIPE_BUF。否则,您需要确保您是唯一一个写入管道文件的人
信号¶
信号是软件中断/事件
提供一种处理异步事件的方法
现代的Unix系统定义了超过30种不同的信号
- 信号有一种处理或行动的概念
忽略信号:适用于除SIGKILL、SIGSTOP和SIGEMT之外的所有信号
捕捉信号:程序向内核注册一个处理信号的函数。
默认:允许信号执行其默认操作。每个信号都有一个默认操作
重要信号一览表¶
讯号 |
描述 |
---|---|
SIGABRT |
异常终止,由Abort()函数生成。DEFAULT终止并核心转储进程。 |
SIGALRM |
在计时器超时时生成。由Alarm()函数设置。 |
SIGBUS |
表示硬件故障。经常出现内存保护故障。默认终止应用程序。 |
SIGCHLD |
当子进程终止时发送到父进程。 |
SIGCONT |
继续时发送到已停止的进程。 |
SIGEMT |
一般硬件故障 |
SIGFPE |
算术异常:被0除、浮点溢出等... |
SIGHUP |
终端已断开连接 |
SIGILL |
该进程执行了非法指令(如尝试禁用中断) |
SIGINT |
按Ctrl-C组合键时由终端生成 |
SIGAIO |
在发生异步I/O事件时生成。 |
SIGKILL |
不能被抓住也不能被忽视。默认操作是终止进程。 |
SIGPIPE |
在写入读取器已终止的管道时生成。 |
SIGSEGV |
分割违规 |
SIGSTOP |
在进程进入停止状态之前发送。 |
处理信号-示例¶
1static void handler(int);
2
3int main(int argv, char* argv[]) {
4 signal(SIGUSR1, handler);
5 signal(SIGINT, handler);
6 while(1) { pause(); }
7}
8
9static void handler(int signalNum) {
10 if(signalNum == SIGUSR1) {
11 printf("received SIGUSR1\n");
12 } else if(signalNum == SIGINT) {
13 printf("received SIGHUP\n");
14 }
15}
发送信号-示例¶
可以使用KILL将信号发送到正在运行的进程:
$ firefox &
[1] 5050
$ kill -USR1 5050 - sends signal SIGUSR to firefox
$ kill 5050 - sends signal SIGTERM to firefox
信号中断的系统调用¶
信号将“唤醒”阻塞调用以“减慢”系统调用
在旧的Unix中,这是任何阻塞的系统调用。
- 在现代的Unix中,以下是“慢”的系统调用,这意味着它们在理论上可以永远阻止:
从管道、终端设备和网络设备读取
如果无法立即接受数据,则写入管道、终端设备和网络设备。
打开文件,这些文件会被阻止,直到出现某些情况(例如,串行设备或调制解调器连接)。
对PAUSE()函数的所有调用都会等待,直到捕获到信号。
这意味着,如果您正在捕获信号,则如果这些函数返回错误状态,则必须检查errno以查找EINTR(被信号中断)错误。如果是,则必须重试操作
在较新的UNIX系统中,如果在将SA_RESTART标志传递给sigaction(...)的情况下触发信号,则系统调用将在信号处理完成后自动重新启动。
信号-重入函数¶
在处理信号时,进程的主要执行被挂起。中断状态不是可以检查或以其他方式确定的。
中断的状态可能在一个Malloc()或Free()调用中。如果在信号处理程序中调用Malloc()或Free(),可能会损坏已分配列表或空闲列表。
因此,在信号处理程序中,我们只能调用可重入函数。
- 可重入函数具有以下属性:
不要调用Malloc()或Free()
不要引用可变的静态数据结构
经验法则是,调用系统调用或系统调用的低级别包装的函数是安全的。除非您确定其他功能的安全性,否则不应使用这些功能。
信号-重入函数¶
abort access alarm chdir chmod chown
close creat dup dup2 execle execve
exit fcntl fork fstat getgid getuid
kill link longjmp lseek mkdir mkfifo
open pathconf pause pipe read rename
rmdir setgid setsid setuid sigaction sigaddset
sigdelset sigemptyset signal sigpending sigsuspend sleep
stat sysconf time times umask uname
unlink utime wait waitpid write
信号-以代码发送¶
- 向另一个进程发送信号:
INT KILL(PIDT PID,INT SIGNO);
Id>0,向id=id=id的进程发送信号
Pid=0,向组ID与信号发送方相同的所有进程发送信号。不会发送到init或swapper后台进程。
Id<0,则向其进程组ID等于发送方有权发送信号的id的绝对值的所有进程发送信号。
PID=-1,未定义。
- 向您自己的进程发送信号:
INT RAISE(INT SIGNO);
- 发送SIGALRM(默认情况下终止进程):
INT ALARM(无符号INT秒);
- 正在等待信号:
INT PAUSE(空);
示例-实现带有警报的Slear()¶
1 void sig_alrm(int signo)
2 {
3 /* ... */
4 }
5
6 unsigned int sleepFor(int numSeconds) {
7 signal(SIGALRM, sig_alrm); //set signal handler
8 alarm(numSeconds); //set alarm for n-seconds
9 pause(); //wait for signal
10 return alarm(0); //turn off alarm
11 handler;
12 }
示例-“更好”的睡眠实施¶
1jmpbuf env_alrm;
2
3void sig_alrm(int signo) {
4 longjmp(env_alrm, 1);
5}
6
7unsinged int sleepFor(int numSeconds) {
8 signal(SIGALRM, sig_alrm);
9 if(setjmp(env_alrm) == 0) {
10 alarm(nsecs);
11 pause();
12 }
13 return alarm(0);
14}
演示睡眠问题¶
1void sig_int(int signo) {
2 volatile int j = 0;
3 printf("sig_int enter\n");
4 for(int i = 0; i < 10000000; i++) {
5 j += i * i;
6 }
7 printf("sig_int done\n");
8}
9
10int main(int argc, char* argv[]) {
11 signal(SIGINT, sig_int);
12 unsigned int unslept = sleepFor(1);
13 printf("sleepFor returned: %u\n", unslept);
14 return 0;
15}
16
睡眠的历史以及为什么信号是可怕的。¶
这两个休眠实现类似于Unix中的Slear()调用的历史实现
这两种情况都表明,信号处理程序的实现需要非常小心地处理。
在信号和暂停之间,你必须记住以下几点:在信号登记和暂停呼叫之间,你可能会收到信号。因此,暂停可能会永久阻止
- 在信号处理程序中,您必须记住以下内容:
您可能正在中断主程序或另一个信号处理程序的执行。
避免修改全局变量
- 避免不可重入函数
注意堆栈修改函数,如setjMP或LongjMP
如果您需要此行为,请查看sigsetjmp或siglong jmp
注意您的处理程序生成的信号
警报的其他用途(...)¶
除了使用Alarm()实现Slear()调用
Alarm()可用于为慢速或阻塞操作设置时间上限。
如果我们希望确保不会一直等待调用从命名管道中读取数据,我们可以设置一个警报调用。
内存映射文件¶
内存映射文件是在应用程序之间共享内存区域的两种方式之一。
- 首先,我们需要介绍一些虚拟内存概念:
虚拟地址-程序在内存中看到的对象的地址
物理地址-对象在物理内存中的真实地址(如果存在
物理地址和虚拟地址几乎完全不同。
后备存储-支持物理内存对象的非物理内存位置。
- All 物理内存对象有后备存储
文本段-可执行文件和库文件
数据、堆栈段-交换文件
内存映射区域-内存映射文件
我们将在后面的课程中更深入地探讨虚拟内存,但这些概念足以继续
内存映射文件¶
内存映射文件中使用的技术很简单,但功能非常强大。
基本上,当您使用mmap()函数对一个文件进行内存映射时,您就是在声明一个由文件支持的虚拟内存区域。
这意味着,当您写入此映射区域时,这些值将立即出现在备份文件中(对其他程序而言)。如果备份文件被更新,该文件中的值将(对于运行的程序)立即出现在内存中。
当两个程序将一个文件映射到虚拟内存时,两个程序之间的虚拟地址很可能不同。
将内存映射文件的相同区域映射到内存的两个程序将能够通过读取和写入该内存区域的值来相互通信
内存映射文件¶
内存映射文件-虚拟地址¶
在虚拟内存中,有两个实例必须使用与位置无关的代码(使用相对内存寻址)。
共享库-因为数据/BSS段在进程之间共享,并映射到不同的虚拟地址。
内存映射文件-因为内存映射文件在不同进程中映射到不同的虚拟地址。
要实现与位置无关的代码,您编写的代码必须使用与GCC中使用的-fPIC类似的技术来处理内存映射区域。
- 在内存映射区中写入位置无关代码的规则:
不要传递使用指针的指针或结构。指针使用虚拟地址。
内存映射区域中的对象不应引用内存映射区域之外的对象。
绝对存储器寻址¶
1typedef struct {
2 char[50] Brand;
3 char[2] TractionRating;
4 char[2] SpeedRating;
5} Wheel;
6
7typedef struct {
8 Wheel*[4] Wheels;
9 char* Make;
10 char* Model;
11} Car;
12
13Car* car = (Car*)malloc(sizeof(Car));
14car.Make = (char*)malloc(sizeof(char)*50);
15car.Model = (char*)malloc(sizeof(char)*50);
16car.Wheels[0] = (Wheel*)malloc(sizeof(Wheel));
17car.Wheels[1] = (Wheel*)malloc(sizeof(Wheel));
18car.Wheels[2] = (Wheel*)malloc(sizeof(Wheel));
19car.Wheels[3] = (Wheel*)malloc(sizeof(Wheel));
20
21car.Wheels[1]->Brand
在这里,每个车轮对象的位置与汽车的绝对引用一起存储。如果它在内存映射文件中共享,则调用car.Wheels [0] 不会解析到正确的地址。
相对内存寻址¶
1typedef struct {
2 char[50] Brand;
3 char[2] TractionRating;
4 char[2] SpeedRating;
5} Wheel;
6
7typedef struct {
8 Wheel[4] Wheels;
9 char[50] Make;
10 char[50] Model;
11} Car;
12
13Car* car = (Car*)malloc(sizeof(Car));
在这里,我们使用的是相对寻址。整个car结构被分配在一个连续的内存区中。要访问每个车轮,只需使用汽车结构开始处的偏移量。
Linux/Minix-mmap()的使用¶
通用算法:
进程A在磁盘上创建文件
进程A查找长度N
进程A将0写入文件
进程A对文件调用mmap以将其映射到内存
进程A将其数据结构复制到映射区域
进程B对文件调用mmap以将其映射到内存
进程A和进程B使用映射区域进行协作
Mmap-简单示例¶
1int main(int argc, char* argv[]) {
2 const char *data = "Hello World";
3 const int dummyValue = 0;
4 int dataSize = sizeof(char) * strlen(data) + sizeof(char);
5
6 int fd = open("shared.dat", O_CREAT|O_TRUNC|O_RDWR, 0666);
7 lseek(fd, dataSize, SEEK_SET);
8 write(fd, (char*)&dummyValue, sizeof(char));
9
10 void* map = mmap(NULL,dataSize,PROT_READ|PROT_WRITE,MAP_SHARED,fd,0);
11
12 memcpy(map, data, dataSize);
13
14 getchar();
15
16 munmap(map, dataSize);
17 close(fd);
18
19 return 0;
20}
21
Mmap-简单示例¶
1int main(argc, char* argv[]) {
2 struct stat fileStat;
3 stat("shared.dat", &fileStat);
4 int fileSize = fileStat.st_size;
5
6 int fd = open("shared.dat", O_RDWR);
7
8 void* map = mmap(NULL, fileSize, PROT_READ|PROT_WRITE, MAP_SHARED, fd, 0);
9
10 char* data = (char*)calloc(1, fileSize);
11
12 memcpy(data, map, fileSize);
13
14 printf("%s\n", data);
15
16 free(data);
17 munmap(map);
18 close(fd);
19
20 return 0;
21}
22
内存映射文件-原子性¶
当我们研究管道时,我们了解到写入管道(低于某个限制)是一种原子操作。我们得到这个保证是因为操作系统内核通过保证系统调用是原子的来保证这一点。
内存映射文件中唯一涉及的系统调用是mmap和munmap。它们仅用于初始化和清理映射的内存区域。
读取和写入映射的内存区域不涉及系统调用,也不保证原子性。它类似于多个线程写入同一堆。
- 要在内存映射区域中实现原子性,您必须使用:
Mutex
信号量
监视器/条件变量
您使用的实现还必须在内部使用相对寻址,或者能够为其配置相对寻址。
内存映射区域不能保证原子性,也不使用上下文切换,这也是内存映射区域表现如此出色的原因。
一般来说,一种机制提供的保障越少,它就越快。
内存映射文件-限定的缓冲区¶
1#include <semaphore.h>
2
3const int MessageQueueSize = (5);
4const int MaxMessageSize = (20);
5
6class Message {
7public:
8 ~Message();
9 void EnqueueMessage(const char *msg);
10 char* DequeueMessage();
11 static Message *CopyToMemoryMappedFile(int fd);
12 static Message *GetFromMemoryMappedFile(int fd);
13static void ReleaseFile(Message *msg, int fd);
14private:
15 Message();
16 sem_t _lock;
17 sem_t _empty;
18 sem_t _full;
19 int _current;
20 char _messages[MessageQueueSize][MaxMessageSize];
21};
22
内存映射文件-限定的缓冲区¶
1Message::Message() {
2 sem_init(&_lock, 1, 1); //passing 1 as the 2nd parameter allows the
3 sem_init(&_empty, 1, 0); //semaphore work in a memory mapped region
4 sem_init(&_full, 1, MessageQueueSize);
5 _current = 0;
6}
7Message::~Message() { }
8Message *Message::CopyToMemoryMappedFile(int fd) {
9 int datasize = sizeof(Message);
10 printf("message size = %d\n", datasize);
11 if(lseek(fd, sizeof(Message), SEEK_SET) == (-1)) {
12 fprintf(stderr, "error in lseek\n");
13 }
14 int dummyVal = 0;
15 if(write(fd, (char*)&dummyVal, sizeof(char)) == (-1)) {
16 fprintf(stderr, "error in write\n");
17 }
18 void *map = mmap(NULL, sizeof(Message), (PROT_READ|PROT_WRITE), MAP_SHARED, fd, 0);
19 if(map == (void*)(-1)) {
20 fprintf(stderr, "mmap() returned -1\n");
21 }
22 Message *msg = new Message();
23 memcpy(map, (void*)msg, sizeof(Message));
24 delete msg;
25 return (Message*)map;
26}
27
内存映射文件-限定的缓冲区¶
1Message *Message::GetFromMemoryMappedFile(int fd) {
2 void *map = mmap(
3 NULL, sizeof(Message),
4 (PROT_READ|PROT_WRITE), MAP_SHARED, fd, 0);
5 if(map == (void*)(-1)) {
6 fprintf(stderr, "mmap() returned -1\n");
7 }
8 Message* msg = (Message*)map;
9 return msg;
10}
11
12void Message::ReleaseFile(Message *msg, int fd) {
13 if(munmap((void*)msg, sizeof(Message)) == (-1)) {
14 fprintf(stderr, "munmap() failed\n");
15 }
16}
17
内存映射文件-限定的缓冲区¶
1void Message::EnqueueMessage(const char *msg) {
2 sem_wait(&_full);
3 sem_wait(&_lock);
4 _current += 1;
5 bzero(&_messages[_current], MaxMessageSize*sizeof(char));
6 memcpy(&_messages[_current], msg, strlen(msg)*sizeof(char));
7 sem_post(&_lock);
8 sem_post(&_empty);
9}
10
11char* Message::DequeueMessage() {
12 char *msg = new char[MaxMessageSize];
13 sem_wait(&_empty);
14 sem_wait(&_lock);
15 memcpy(msg, &_messages[_current], MaxMessageSize*sizeof(char));
16 _current -= 1;
17 sem_post(&_lock);
18 sem_post(&_full);
19 return msg;
20}
21
内存映射文件-限定的缓冲区¶
生产者代码
1int main(int argc, char* argv[]) {
2 const char *sharedFileName = "shared.dat";
3 const mode_t mode = 0666;
4 const int openFlags = (O_CREAT | O_TRUNC | O_RDWR);
5 int fd = open(sharedFileName, openFlags, mode);
6
7 if(fd == (-1)) {
8 printf("open returned (-1)\n");
9 return (-1);
10 }
11
12 Message* msg = Message::CopyToMemoryMappedFile(fd);
13
14 for(int i = 0; i < 100; i++) {
15 char message[10];
16 sprintf(message, "%d\n", i);
17 msg->EnqueueMessage(&message[0]);
18 printf("enqueued %d\n", i);
19 }
20 printf("message queue written\n");
21 getchar();
22 Message::ReleaseFile(msg, fd);
23 close(fd);
24}
内存映射文件-限定的缓冲区¶
消费者代码
1int main(int argc, char* argv[]) {
2 const char *sharedFileName = "shared.dat";
3 const mode_t mode = 0666;
4 const int openFlags = (O_RDWR);
5 int fd = open(sharedFileName, openFlags, mode);
6
7 if(fd == (-1)) {
8 printf("open returned (-1)\n");
9 return (-1);
10 }
11
12 Message* msg = Message::GetFromMemoryMappedFile(fd);
13
14 int count = 0;
15
16 while(1) {
17 char *message = msg->DequeueMessage();
18 printf("%d: %s", ++count, message);
19 fflush(stdout);
20 }
21
22 Message::ReleaseFile(msg, fd);
23
24 close(fd);
25}
内存映射文件-快速I/O¶
内存映射I/O速度更快,因为它避免了从用户模式到内核模式的复制
普通用户-内核写入(...)呼叫算法:
USER APP-WRITE(fd,user_buf,len);
用户应用-上下文切换到操作系统(软件中断)
内核模式-在文件中分配空间,检查安全性等。
内核模式-将USER_BUF复制到FS缓冲区缓存
内核模式-上下文切换到用户应用程序(中断返回)
稍后,内核将缓冲区高速缓存提交到磁盘
普通用户-内核mmap(...)写入算法:
用户应用程序-将值复制到映射区域
内核模式-MMU触发页面错误(硬件中断)
内核模式-将页面写入后备存储
内核模式-上下文切换到用户应用程序(中断返回)
内存映射文件-快速I/O¶
常规读取与mmap读取的读取算法与写入算法非常相似,也避免了内核到用户模式的复制
在具有大地址空间(64位)的系统上,内存映射I/O可能非常有利。-例如,数据库服务器可以将几TB大小的整个数据库内存映射到内存中。
由于大多数VM系统使用非常高效的LRU算法,并且具有大量的I/O调度数据,因此内存映射大文件是最快的方法之一。
其他高级方法包括分散/聚集或向量化I/O。
当文件的结构很好地映射到域模型时,内存映射I/O具有最大的优势。这意味着不需要序列化/反序列化。
文件-IPC¶
文件是最古老、最常用的IPC形式
现代操作系统中的几乎所有资源都可以通过基于文件的约定(打开、读取、写入、查找、关闭)进行访问
基于文件的IPC在几乎所有操作系统中都可用。
- 在现代,基于文件的IPC最好考虑以下几种模式:
状态持久化-超越程序的生命周期
公开当前状态-在程序执行期间
队列/假脱机文件夹-邮件守护程序、打印机、执行队列
资源状态表达式-锁定文件、可用性等...
文件-显示当前状态¶
/proc文件系统是操作系统设计人员公开系统信息而无需发明新的系统调用的重要方法。
对于内核模块开发人员和设备驱动程序开发人员来说,这是一个关键优势
还提供了一种调试内核更改的好方法
文件系统 |
描述 |
---|---|
文件系统 |
描述 |
/proc/vmstat |
虚拟内存统计信息和配置 |
/proc/cpuinfo |
单个CPU信息 |
/proc/<PID> |
单个进程信息 |
/proc/loadavg |
就绪进程负载的移动平均值 |
假脱机文件夹¶
- 假脱机文件夹最常用于:
电子邮件守护进程(后缀、Exchange等)
打印机管理器(CUPS、LPR等)
作业管理器(CRON等)
假脱机文件夹是为单个进程保留的文件夹,该进程的单一任务是监视文件夹并处理在该文件夹中创建的每个新文件。文件夹中的每个文件代表一项要完成的任务。
假脱机文件夹与其他持久化排队系统一样,对故障具有弹性。如果处理守护进程崩溃,它可以在不丢失任务列表的情况下重新启动。
通常,假脱机文件夹是按某个定义的顺序处理的。一个典型的顺序是按字母顺序处理文件。
如果不打算重复这些作业,则文件通常会在完成或删除后移动到另一个文件夹。
假脱机文件夹-Cron¶
Cron通常在/etc下维护几个假脱机文件夹:
/etc/cron.daily
/etc/cron.hourly
/etc/cron.monthly
/etc/cron.weekly
Cron将在正确的时间进入每个文件夹,并执行每个文件夹中的每个可执行文件。
线轴文件夹-杯子¶
CUPS是用于UNIX操作系统的打印机守护程序
杯子的假脱机文件夹通常是/var/spool/cups
- 假脱机文件夹中有两种类型的文件:
- 数据文件-正在打印的对象
名称为d00001-001、d00001-002、d00002-001、.
- 控制文件-表示一组数据文件的文件
名称为c00001,c00002,...
此文件命名约定的使用公开了CUPS系统在其队列中遵守的各种域模型。
CUPS有一系列程序来管理假脱机文件夹中文件的创建和假脱机文件夹中的文件处理。
锁定文件¶
所有系统调用的一个有用方面是它们是原子操作。这意味着一些系统调用可用于执行类似测试和设置的操作,并成为锁定系统的基础。
- 一个常见的例子是使用文件来确保一次只有一个软件程序实例在运行。
例如,您不希望在同一端口上运行HTTP守护进程。
为了防止出现这种情况,您可以创建一个类似/var/lock/http_80的锁定文件。
当HTTP守护进程启动时,它可以在开始之前检查配置它的端口的/var/lock/http_##。当守护进程关闭时,它可以删除该文件
文件的创建和删除是一个原子操作。因此,如果两个进程同时尝试创建相同的文件,则只有一个进程会成功。
门¶
门几乎只存在于Solaris上,而不存在于其他地方
即便如此,它们仍是一个有趣的概念。
DOORS基本上允许进程通过文件系统上的一个或多个文件公开一个或多个函数。它基本上是基于文件系统的RPC机制
服务器代码¶
1void server(void* cookie, char* argp, size_t arg_size, door_desc_t* dp, uint_t n_desc) {
2
3 /*server code goes here*/
4
5}
6
7int doorfd = door_create(server, 0, 0);
8int fd = creat("/tmp/door", 0666);
9fdetach("/tmp/door");
10fattach(doorfd,"/tmp/door");
11pause();
12
客户代码¶
1typdef struct myArg {
2 int id
3} myArg_t;
4
5door_arg_t d_arg;
6int doorfd = open("/tmp/door", O_RDONLY);
7myArg_t* arg = (myArg_t*)malloc(sizeof(myArg_t));
8arg->id = 12;
9d_arg.data_ptr = (char*)arg;
10d_arg.data_size = sizeof(*arg);
11d_arg.desc_ptr = NULL;
12d_arg.desc_num = 0;
13door_call(doorfd, &d_arg);
域套接字¶
- 域套接字与命名管道的用途相同,不同之处在于域套接字:
全双工
一台服务器可以有多个客户端
支持数据报和流模式
- 域套接字使用的函数与Internet套接字使用的函数非常相似:
接受、绑定、侦听、连接、套接字
域套接字的设置可能有点复杂,因此最好的解释方式是通过一个简单的示例。
域套接字-示例¶
1int server_listen(const char *fileName) {
2 unlink(fileName);
3 int socket_fd = socket(PF_UNIX, SOCK_STREAM, 0);
4 struct sockaddr_un address;
5 memset(&address, 0, sizeof(struct sockaddr_un));
6 address.sun_family = AF_UNIX;
7 sprintf(address.sun_path, fileName);
8
9 bind(socket_fd, (struct sockaddr*)&address, sizeof(struct sockaddr_un));
10 listen(socket_fd, 5);
11 int connection_fd;
12 socklen_t address_length;
13 while((connection_fd = accept(socket_fd, (struct sockaddr*)&address, &address_length)) > (-1)) {
14 int child = fork();
15 if(child == 0) {
16 return connection_handler(connection_fd);
17 } else {
18 close(connection_fd);
19 }
20 }
21
22 close(socket_fd);
23 unlink(fileName);
24 return 0;
25}
域套接字-示例¶
1int connection_handler(int socket_fd) {
2 char buff[256];
3 int nBytes = read(socket_fd, buff, 256);
4 buff[nBytes] = 0;
5 printf("message from client: %s\n", buff);
6 nBytes = snprintf(buff, 256, "hello from server");
7 write(socket_fd, buff, nBytes);
8
9 close(socket_fd);
10 return 0;
11}
12
域套接字-示例¶
1int client_connect(const char* fileName) {
2 int socket_fd = socket(PF_UNIX, SOCK_STREAM, 0);
3
4 struct sockaddr_un address;
5 memset(&address, 0, sizeof(struct sockaddr_un));
6 address.sun_family = AF_UNIX;
7 sprintf(address.sun_path, fileName);
8
9 connect(socket_fd, (struct sockaddr*)&address, sizeof(struct sockaddr_un));
10 char buffer[256];
11 int nBytes = snprintf(buffer, 256, "hello from a client");
12 write(socket_fd, buffer, nBytes);
13
14 nBytes = read(socket_fd, buffer, 256);
15 buffer[nBytes] = 0;
16
17 printf("message from server: %s\n", buffer);
18
19 close(socket_fd);
20
21 return 0;
22}
23