当前位置: 首页 > news >正文

c2c网站代表有哪些广告优化师怎么入行

c2c网站代表有哪些,广告优化师怎么入行,深圳创业项目,淄博机关建设网站参考引用 UNIX 环境高级编程 (第3版)嵌入式Linux C应用编程-正点原子 1. 非阻塞 I/O 阻塞就是进入了休眠状态#xff0c;交出了 CPU 控制权阻塞 I/O 就是对文件的 I/O 操作#xff08;读写操作#xff09;是阻塞式的#xff0c;非阻塞 I/O 就是对文件的 I/O 操作是非阻塞的… 参考引用 UNIX 环境高级编程 (第3版)嵌入式Linux C应用编程-正点原子 1. 非阻塞 I/O 阻塞就是进入了休眠状态交出了 CPU 控制权阻塞 I/O 就是对文件的 I/O 操作读写操作是阻塞式的非阻塞 I/O 就是对文件的 I/O 操作是非阻塞的 阻塞式 I/O对于某些文件类型读管道文件、网络设备文件和字符设备文件当对文件进行读操作时如果数据未准备好或文件当前无数据可读那么读操作可能会使调用者阻塞直到有数据可读时才会被唤醒非阻塞式 I/O即使没有数据可读也不会被阻塞而是会立马返回错误 普通文件不管读写多少个字节数据read() 或 write() 一定会在有限的时间内返回所以普通文件总是以非阻塞的方式进行 I/O 操作但管道文件、设备文件等既可使用阻塞式 I/O 操作也可使用非阻塞式 I/O 进行操作 1.1 阻塞 I/O 与非阻塞 I/O 读文件 以读取鼠标为例鼠标是一种输入设备其对应的设备文件在 /dev/input/ 目录下$ cd /dev/input $ ls -lh 总用量 0 drwxr-xr-x 2 root root 80 1月 8 20:18 by-id drwxr-xr-x 2 root root 180 1月 8 20:18 by-path crw-rw---- 1 root input 13, 64 1月 8 20:18 event0 crw-rw---- 1 root input 13, 65 1月 8 20:18 event1 crw-rw---- 1 root input 13, 66 1月 8 20:18 event2 crw-rw---- 1 root input 13, 67 1月 8 20:18 event3 crw-rw---- 1 root input 13, 68 1月 8 20:18 event4 crw-rw---- 1 root input 13, 69 1月 8 20:18 event5 crw-rw---- 1 root input 13, 70 1月 8 20:18 event6 crw-rw-r-- 1 root input 13, 0 1月 8 20:18 js0 crw-rw-r-- 1 root input 13, 1 1月 8 20:18 js1 crw-rw---- 1 root input 13, 63 1月 8 20:18 mice crw-rw---- 1 root input 13, 32 1月 8 20:18 mouse0 crw-rw---- 1 root input 13, 33 1月 8 20:18 mouse1 crw-rw---- 1 root input 13, 34 1月 8 20:18 mouse2# Ubuntu 中普通用户无法对设备文件进行读取或写入操作因此需要加 sudo 执行 # 当执行命令之后移动鼠标或按下鼠标、松开鼠标都会在终端打印出相应的数据证明 mouse2 是鼠标对应的设备文件 $ sudo od -x /dev/input/mouse2 0000000 c138 2881 8100 0028 2881 fd00 ff18 1800 0000020 7ffd 0008 087f 3900 fe38 38fd fefe fd38 0000040 38fd f7f7 f238 38f2 f2f3 ef38 38f0 f0f0 0000060 ec38 38ef c7ae d238 38f3 ffe9 ed18 1800 0000100 08eb eb18 180b 09f5 f618 180a 06fd fe18 0000120 0808 2a00 0408 0813 1807 0d08 0823 3e1c 0000140 0c08 0814 271d 1008 080f 090f 0608 0802 0000160 0105 0408 0800 0003 0128 28fd fd02 0028 0000200 28ff f703 0128 28fe f803 0128 28fd f802 0000220 0128 28fa fe01 0028 28fe fd03 0128 28fe 0000240 ff01 0128 28ff fe03 0108 0800 0001 0108 0000260 0800 0001 0108 0800 0001 0108 0800 0003 0000300 1208 0801 0001 0208 0800 0001 0408 0800 0000320 0001 0208 0800 0005 0728 28ff fe0c 0528 0000340 28fd fb0d 0928 28fa fd05 0328 28fc f806 0000360 0828 28e5 f600 0128 28fa ed00 0028 38f3 0000400 d5f6 fc38 38f3 f8fd fe38 38fa f6fb fb38 0000420 38f1 fbfd ff38 38fc fcfe 0028 28ff ff00 0000440 ff18 2800 ff00 0028 28ff ff00 ff38 28ff1.1.1 阻塞式 I/O 读取鼠标数据 #include stdio.h #include stdlib.h #include sys/types.h #include sys/stat.h #include fcntl.h #include unistd.h #include string.hint main(void) {char buf[100];int fd, ret;// 打开文件fd open(/dev/input/mouse2, O_RDONLY);if (fd -1) {perror(open error);exit(-1);}// 读文件memset(buf, 0, sizeof(buf));ret read(fd, buf, sizeof(buf));if (ret 0) {perror(read error);close(fd);exit(-1);}printf(成功读取%d个字节数据\n, ret);/* 关闭文件 */close(fd);exit(0); }$ gcc block.c -o block # 执行程序没有立即结束而是一直占用了终端没有输出信息原因在于调用 read() 之后进入了阻塞状态因为当前鼠标没有数据可读 # 如果此时移动鼠标、或者按下鼠标上的任何一个按键阻塞会结束read() 会成功读取到数据并返回 $ sudo ./block 成功读取3个字节数据 $1.1.2 非阻塞式 I/O 读取鼠标数据 ... fd open(/dev/input/event3, O_RDONLY | O_NONBLOCK); ...$ gcc block2.c -o block2 $ sudo ./block2 # 执行程序后立马就结束了并且调用 read() 返回错误 # 意思就是说资源暂时不可用原因在于调用 read() 时如果鼠标并没有移动或者被按下没有发生输入事件是没有数据可读故而导致失败返回这就是非阻塞 I/O read error: Resource temporarily unavailable1.1.3 轮训非阻塞式 I/O 读取鼠标数据 #include stdio.h #include stdlib.h #include sys/types.h #include sys/stat.h #include fcntl.h #include unistd.h #include string.hint main(void) {char buf[100];int fd, ret;/* 打开文件 */fd open(/dev/input/mouse2, O_RDONLY | O_NONBLOCK);if (-1 fd) {perror(open error);exit(-1);}/* 轮训读文件 */memset(buf, 0, sizeof(buf));for (;;) {ret read(fd, buf, sizeof(buf));if (0 ret) {printf(成功读取%d个字节数据\n, ret);close(fd);exit(0);}} }1.2 阻塞 I/O 的优缺点 当对文件进行读取操作时如果文件当前无数据可读那么阻塞式 I/O 会将调用者应用程序挂起、进入休眠阻塞状态直到有数据可读时才会解除阻塞而对于非阻塞 I/O应用程序不会被挂起而是会立即返回它要么一直轮训等待直到数据可读要么直接放弃优点能提升 CPU 的处理效率当自身条件不满足时进入阻塞状态并交出 CPU 资源将 CPU 资源让给别人使用而非阻塞式则是抓紧利用 CPU 资源如不断地去轮训这样就会导致该程序占用非常高的 CPU 使用率 1.3 使用非阻塞 I/O 实现并发读取 阻塞式同时读取鼠标和键盘#include stdio.h #include stdlib.h #include sys/types.h #include sys/stat.h #include fcntl.h #include unistd.h #include string.h#define MOUSE /dev/input/mouse2int main(void) {char buf[100];int fd, ret;/* 打开鼠标设备文件 */fd open(MOUSE, O_RDONLY);if (fd -1) {perror(open error);exit(-1);}/* 读鼠标 */memset(buf, 0, sizeof(buf));ret read(fd, buf, sizeof(buf));printf(鼠标: 成功读取%d个字节数据\n, ret);/* 读键盘 */memset(buf, 0, sizeof(buf));ret read(0, buf, sizeof(buf));printf(键盘: 成功读取%d个字节数据\n, ret);/* 关闭文件 */close(fd);exit(0); } $ gcc test.c -o test # 需要先动鼠标在按键盘按下键盘上的按键、按完之后按下回车这样才能既成功读取鼠标、又成功读取键盘 $ sudo ./test 鼠标: 成功读取3个字节数据 hunan 键盘: 成功读取6个字节数据因为 read() 此时是阻塞式读取先读取了鼠标没有数据可读将会一直被阻塞后面的读取键盘将得不到执行这就是阻塞式 I/O 的一个困境无法实现并发读取同时读取主要原因在于阻塞 方法一使用多线程一个线程读取鼠标、另一个线程读取键盘方法二者创建一个子进程父进程读取鼠标、子进程读取键盘方法三使用非阻塞式 I/O 非阻塞式方式同时读取鼠标和键盘#include stdio.h #include stdlib.h #include sys/types.h #include sys/stat.h #include fcntl.h #include unistd.h #include string.h#define MOUSE /dev/input/mouse2int main(void) {char buf[100];int fd, ret, flag;/* 非阻塞方式打开鼠标设备文件 */fd open(MOUSE, O_RDONLY | O_NONBLOCK);if (fd -1) {perror(open error);exit(-1);} // 标准输入文件描述符键盘是从其父进程进程而来并不是在程序中调用 open() 打开得到/* 将键盘设置为非阻塞方式 */flag fcntl(0, F_GETFL); // 先获取原来的 flagflag | O_NONBLOCK; // 将 O_NONBLOCK 标准添加到 flagfcntl(0, F_SETFL, flag); // 重新设置 flagfor (;;) {/* 读鼠标 */ret read(fd, buf, sizeof(buf));if (ret 0) {printf(鼠标: 成功读取%d个字节数据\n, ret);}/* 读键盘 */ret read(0, buf, sizeof(buf));if (ret 0) {printf(键盘: 成功读取%d个字节数据\n, ret);}}/* 关闭文件 */close(fd);exit(0); }$ gcc test2.c -o test2 # 不管是先动鼠标还是先按键盘都可以成功读取到相应数据 $ sudo ./test2 hunan # 键盘输入 键盘: 成功读取6个字节数据 鼠标: 成功读取3个字节数据 鼠标: 成功读取3个字节数据 鼠标: 成功读取3个字节数据 changsha # 键盘输入 键盘: 成功读取9个字节数据 鼠标: 成功读取3个字节数据 鼠标: 成功读取3个字节数据 鼠标: 成功读取3个字节数据2. I/O 多路复用 虽然使用非阻塞式 I/O 解决了阻塞式 I/O 情况下并发读取文件所出现的问题但使得程序的 CPU 占用率特别高为了解决这个问题就要用到 I/O 多路复用方法 2.1 定义 I/O 多路复用通过一种机制可以监视多个文件描述符一旦某个文件描述符也就是某个文件可以执行 I/O 操作时能够通知应用程序进行相应的读写操作 I/O 多路复用技术是为了解决在并发式 I/O 场景中使进程或线程不阻塞于某个特定的 I/O 系统调用I/O 多路复用一般用于并发非阻塞 I/O多路非阻塞 I/O如既要读取鼠标、又要读取键盘I/O 多路复用特征外部阻塞式内部监视多路 I/O采用两个功能几乎相同的系统调用来执行 I/O 多路复用操作分别是 select() 和 poll() 2.2 select() 函数 调用 select() 函数将阻塞直到有以下事情发生 readfds、writefds 或 exceptfds 指定的文件描述符中至少有一个成为就绪态该调用被信号处理函数中断参数 timeout 中指定的时间上限已经超时 #include sys/select.h// nfds在 readfds、writefds 和 exceptfds 3 个描述符集中找出最大描述符编号值然后加 1 // timeout可用于设定 select() 阻塞的时间上限控制 select 的阻塞行为// 1、可将 timeout 设置为 NULL表示 select() 将会一直阻塞直到某一个或多个文件描述符成为就绪态// 2、也可将其指向一个 struct timeval 结构体对象如果结构体对象中的两个成员变量都为 0// 那么此时 select() 函数不会阻塞只是简单地轮训指定的文件描述符集合看其中是否有就绪的文件描述符并立刻返回// 否则参数 timeout 将为 select() 指定一个等待阻塞时间的上限值// 如果在阻塞期间内文件描述符集合中的某一个或多个文件描述符成为就绪态将会结束阻塞并返回// 如果超过了阻塞时间的上限值select() 函数将会返回 int select(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout);fd_set 数据类型以位掩码的形式来实现是一个文件描述符的集合体 readfds 是用来检测读是否就绪是否可读的文件描述符集合writefds 是用来检测写是否就绪是否可写的文件描述符集合exceptfds 是用来检测异常情况是否发生的文件描述符集合如果上述三个参数都设置为 NULL则可以将 select() 当做为一个类似于 sleep() 休眠的函数来使用通过 select() 函数的最后一个参数 timeout 来设置休眠时间 位掩码 奇怪的知识——位掩码位掩码(BitMask)——介绍与使用 所有关于文件描述符集合的操作都是通过四个宏完成FD_CLR()、FD_ISSET()、FD_SET()、FD_ZERO() #include sys/select.hvoid FD_CLR(int fd, fd_set *set); // 将文件描述符 fd 从参数 set 所指向的集合中移除 int FD_ISSET(int fd, fd_set *set); // 如果 fd 是参数 set 所指向集合中的成员则 FD_ISSET() 返回 true否则返回 false void FD_SET(int fd, fd_set *set); // 将 fd 添加到参数 set 所指向的集合中 void FD_ZERO(fd_set *set); // 将参数 set 所指向的集合初始化为空 // 文件描述符集合有一个最大容量限制由常量 FD_SETSIZE 决定Linux 系统该常量的值为 1024 // 如果要在循环中重复调用 select()必须保证每次都要重新初始化并设置 readfds、writefds、exceptfds 这些集合用法示例 fd_set fset; // 定义文件描述符集合FD_ZERO(fset); // 将集合初始化为空FD_SET(3, fset); // 向集合中添加文件描述符 3 FD_SET(4, fset); // 向集合中添加文件描述符 4 FD_SET(5, fset); // 向集合中添加文件描述符 5返回值 返回 -1表示有错误发生并且会设置 errno 可能的错误码包括 EBADF、EINTR、EINVAL、EINVAL 以及 ENOMEMEBADF 表示 readfds、writefds 或 exceptfds 中有一个文件描述符是非法的EINTR 表示该函数被信号处理函数中断了 返回 0表示在任何文件描述符成为就绪态之前 select() 调用已经超时 这种情况下readfdswritefds 以及 exceptfds 所指向的文件描述符集合都会被清空 返回一个正整数表示有一个或多个文件描述符已达到就绪态 返回值表示处于就绪态的文件描述符的个数在这种情况下每个返回的文件描述符集合都需要检查通过 FD_ISSET() 宏进行检查以此找出发生的 I/O 事件是什么如果同一个文件描述符在 readfdswritefds 以及 exceptfds 中同时被指定且它多于多个 I/O 事件都处于就绪态的话select() 返回三个集合中被标记为就绪态的文件描述符的总数 示例使用 select 实现同时读取键盘和鼠标非阻塞 I/O 方式 #include stdio.h #include stdlib.h #include sys/types.h #include sys/stat.h #include fcntl.h #include unistd.h #include sys/select.h#define MOUSE /dev/input/mouse2int main(void) {char buf[100];int fd, ret 0, flag;fd_set rdfds;int loops 5;/* 打开鼠标设备文件 */fd open(MOUSE, O_RDONLY | O_NONBLOCK);if (-1 fd) {perror(open error);exit(-1);}/* 将键盘设置为非阻塞方式 */flag fcntl(0, F_GETFL); // 先获取原来的 flagflag | O_NONBLOCK; // 将 O_NONBLOCK 标准添加到 flagfcntl(0, F_SETFL, flag); // 重新设置 flag/* 同时读取键盘和鼠标 */while (loops--) {FD_ZERO(rdfds);FD_SET(0, rdfds); // 添加键盘FD_SET(fd, rdfds); // 添加鼠标ret select(fd 1, rdfds, NULL, NULL, NULL);if (ret 0) {perror(select error);goto out;} else if (ret 0) {fprintf(stderr, select timeout.\n);continue;}/* 检查键盘是否为就绪态 */if (FD_ISSET(0, rdfds)) {ret read(0, buf, sizeof(buf));if (ret 0) {printf(键盘: 成功读取%d个字节数据\n, ret);}}/* 检查鼠标是否为就绪态 */if (FD_ISSET(fd, rdfds)) {ret read(fd, buf, sizeof(buf));if (ret 0) {printf(鼠标: 成功读取%d个字节数据\n, ret);}}}out:/* 关闭文件 */close(fd);exit(ret); }2.3 poll() 函数 在 poll() 函数中需要构造一个 struct pollfd 类型的数组每个数组元素指定一个文件描述符以及对该文件描述符所关心的条件数据可读、可写或异常情况 #include poll.hint poll(struct pollfd *fds, nfds_t nfds, int timeout);// 调用者初始化 events 来指定需要为文件描述符 fd 做检查的事件 // 当 poll() 函数返回时revents 变量由 poll() 函数内部进行设置用于说明文件描述符 fd 发生了哪些事件 struct pollfd {int fd; /* 文件描述符 */short events; /* 位掩码requested events */short revents; /* 位掩码returned events */ };fds指向一个 struct pollfd 类型的数组数组中的每个元素都会指定一个文件描述符以及对该文件描述符所关心的条件nfds指定了 fds 数组中的元素个数数据类型 nfds_t 实际为无符号整形timeout该参数与 select() 函数的 timeout 参数相似用于决定 poll() 函数的阻塞行为 timeout -1poll() 会一直阻塞与 select()函数的 timeout 等于 NULL 相同直到 fds 数组中列出的文件描述符有一个达到就绪态或者捕获到一个信号时返回timeout 0poll()不会阻塞只是执行一次检查看看哪个文件描述符处于就绪态timeout 0表示设置 poll() 函数阻塞时间的上限值意味着 poll() 函数最多阻塞 timeout 毫秒直到 fds 数组中列出的文件描述符有一个达到就绪态或者捕获到一个信号为止 poll 的 events 和 revents 标志 一般用的最多的还是 POLLIN 和 POLLOUT 返回值 返回 -1 表示有错误发生并且会设置 errno返回 0 表示该调用在任意一个文件描述符成为就绪态之前就超时了返回一个正整数表示有一个或多个文件描述符处于就绪态返回值表示 fds 数组中返回的 revents 变量不为 0 的 struct pollfd 对象的数量 示例使用 poll 实现同时读取鼠标和键盘非阻塞 I/O 方式 #include stdio.h #include stdlib.h #include sys/types.h #include sys/stat.h #include fcntl.h #include unistd.h #include poll.h#define MOUSE /dev/input/event3int main(void) {char buf[100];int fd, ret 0, flag;int loops 5;struct pollfd fds[2];/* 打开鼠标设备文件 */fd open(MOUSE, O_RDONLY | O_NONBLOCK);if (-1 fd) {perror(open error);exit(-1);}/* 将键盘设置为非阻塞方式 */flag fcntl(0, F_GETFL); // 先获取原来的 flagflag | O_NONBLOCK; // 将 O_NONBLOCK 标准添加到 flagfcntl(0, F_SETFL, flag); // 重新设置 flag/* 同时读取键盘和鼠标 */fds[0].fd 0;fds[0].events POLLIN; // 只关心数据可读fds[0].revents 0;fds[1].fd fd;fds[1].events POLLIN; // 只关心数据可读fds[1].revents 0;while (loops--) {ret poll(fds, 2, -1);if (ret 0) {perror(poll error);goto out;}else if (ret 0) {fprintf(stderr, poll timeout.\n);continue;}/* 检查键盘是否为就绪态 */if (fds[0].revents POLLIN) {ret read(0, buf, sizeof(buf));if (ret 0)printf(键盘: 成功读取%d个字节数据\n, ret);}/* 检查鼠标是否为就绪态 */if (fds[1].revents POLLIN) {ret read(fd, buf, sizeof(buf));if (ret 0)printf(鼠标: 成功读取%d个字节数据\n, ret);}}out:/* 关闭文件 */close(fd);exit(ret); }使用 select() 或 poll() 时需要注意一个问题 当监测到某一个或多个文件描述符成为就绪态可读或写时需要执行相应的 I/O 操作以清除该状态否则该状态会一直存在那么下一次调用 select() 或 poll() 时文件描述符已经处于就绪态了将直接返回 3. 异步 I/O 在 I/O 多路复用中进程通过系统调用 select() 或 poll() 来主动查询文件描述符上是否可以执行 I/O 操作 而异步 I/O 中当文件描述符上可执行 I/O 操作时进程可以请求内核为自己发送一个信号之后进程就可执行任何其它的任务直到文件描述符可执行 I/O 操作为止此时内核会发送信号给进程异步 I/O 通常也称信号驱动 I/O 要使用异步 I/O程序需按照如下步骤执行 通过指定 O_NONBLOCK 标志使能非阻塞 I/O通过指定 O_ASYNC 标志使能异步 I/O设置异步 I/O 事件的接收进程 也就是当文件描述符上可执行 I/O 操作时会发送信号通知该进程通常将调用进程设置为异步 I/O 事件的接收进程 为内核发送的通知信号注册一个信号处理函数 默认情况下异步 I/O 的通知信号是 SIGIO 以上步骤完成之后进程就可以执行其它任务了当 I/O 操作就绪时内核会向进程发送一个 SIGIO 信号当进程接收到信号时会执行预先注册好的信号处理函数就可以在信号处理函数中进行 I/O 操作 3.1 O_ASYNC 标志 O_ASYNC 标志可用于使能文件描述符的异步 I/O 事件当文件描述符可执行 I/O 操作时内核会向异步 I/O 事件的接收进程发送 SIGIO 信号默认情况下 在调用 open() 时无法通过指定 O_ASYNC 标志来使能异步 I/O但可以使用 fcntl() 函数添加 O_ASYNC 标志使能异步 I/O int flag;flag fcntl(0, F_GETFL); // 先获取原来的 flag flag | O_ASYNC; // 将 O_ASYNC 标志添加到 flag fcntl(fd, F_SETFL, flag); // 重新设置 flag3.2 设置异步 I/O 事件的接收进程 为文件描述符设置异步 I/O 事件的接收进程也就是设置异步 I/O 的所有者。同样也是通过 fcntl() 函数进行设置操作命令 cmd 设置为 F_SETOWN第三个参数传入接收进程的进程 IDPID通常将调用进程的 PID 传入fcntl(fd, F_SETOWN, getpid());3.3 注册 SIGIO 信号的处理函数 通过 signal() 或 sigaction() 函数为 SIGIO 信号注册一个信号处理函数当进程接收到内核发送过来的 SIGIO 信号时会执行该处理函数 3.4 示例以异步 I/O 方式读取鼠标 #include stdio.h #include stdlib.h #include sys/types.h #include sys/stat.h #include fcntl.h #include unistd.h #include signal.h#define MOUSE /dev/input/mouse2static int fd;static void sigio_handler(int sig) {static int loops 5;char buf[100] {0};int ret;if (SIGIO ! sig) {return;}ret read(fd, buf, sizeof(buf));if (ret 0) {printf(鼠标: 成功读取%d个字节数据\n, ret);}loops--;if (loops 0) {close(fd);exit(0);} }int main(void) {int flag;/* 打开鼠标设备文件使能非阻塞 I/O */fd open(MOUSE, O_RDONLY | O_NONBLOCK);if (fd -1) {perror(open error);exit(-1);}/* 使能异步 I/O */flag fcntl(fd, F_GETFL);flag | O_ASYNC;fcntl(fd, F_SETFL, flag);/* 设置异步 I/O 的所有者 */fcntl(fd, F_SETOWN, getpid());/* 为 SIGIO 信号注册信号处理函数 */signal(SIGIO, sigio_handler);for (;;) {sleep(1);} }4. 优化异步 I/O 在一个需要同时检查大量文件描述符如数千个的应用程序中例如某种类型的网络服务端程序与 select() 和 poll() 相比异步 I/O 能够提供显著的性能优势 原因对于异步 I/O内核可以 “记住” 要检查的文件描述符且仅当这些文件描述符上可执行 I/O 操作时内核才会向应用程序发送信号 对于 select() 或 poll() 函数来说内部实现原理其实是通过轮训的方式来检查多个文件描述符是否可执行 I/O 操作所以当需要检查的文件描述符数量较多时随之也将会消耗大量的 CPU 资源来实现轮训检查操作 当需要检查的文件描述符并不是很多时使用 select() 或 poll() 是一种非常不错的方案当需要检查大量文件描述符时可以使用 epoll 解决 select()或 poll()性能低的问题 默认异步 I/O 的缺陷 默认的异步 I/O 通知信号 SIGIO 是非排队信号 SIGIO 信号是标准信号非实时信号、不可靠信号不支持信号排队机制如当前正在执行 SIGIO 信号的处理函数此时内核又发送多次 SIGIO 信号给进程这些信号将会被阻塞只有当信号处理函数执行完毕之后才会传递给进程并且只能传递一次而其它后续的信号都会丢失 无法得知文件描述符发生了什么事件 在 3.3 小节代码中的信号处理函数 sigio_handler()直接调用 read() 函数读取鼠标而未判断文件描述符是否处于可读就绪态 4.1 使用实时信号替换默认信号 SIGIO 使用 fcntl() 函数进行设置调用函数时将操作命令 cmd 参数设置为 F_SETSIG第三个参数 arg 指定一个实时信号编号表示将该信号作为异步 I/O 通知信号// 指定了 SIGRTMIN 实时信号作为文件描述符 fd 的异步 I/O 通知信号 // 如果第三个参数 arg 设置为 0则表示指定 SIGIO 信号作为异步 I/O 通知信号 fcntl(fd, F_SETSIG, SIGRTMIN);4.2 使用 sigaction() 函数注册信号处理函数 在应用程序中需要为实时信号注册信号处理函数使用 sigaction 函数进行注册并为 sa_flags 参数指定 SA_SIGINFO表示使用 sa_sigaction 指向的函数作为信号处理函数而不使用 sa_handler 指向的函数。因为 sa_sigaction 指向的函数作为信号处理函数提供了更多的参数可以获取到更多信息 对于异步 I/O 事件而言传递给信号处理函数的 siginfo_t 结构体中与之相关的字段如下 si_signo引发处理函数被调用的信号。这个值与信号处理函数的第一个参数一致si_fd表示发生异步 I/O 事件的文件描述符si_code表示文件描述符 si_fd 发生了什么事件读就绪态、写就绪态或者是异常事件等si_band是一个位掩码其中包含的值与系统调用 poll() 中返回的 revents 字段中的值相同 4.3 示例优化异步 I/O 方式读取鼠标 // 使用实时信号 sigaction 优化异步 I/O #define _GNU_SOURCE // 需要定义了_GNU_SOURCE 宏之后才能使用 F_SETSIG#include stdio.h #include stdlib.h #include sys/types.h #include sys/stat.h #include fcntl.h #include unistd.h #include signal.h#define MOUSE /dev/input/mouse2static int fd;static void io_handler(int sig, siginfo_t *info, void *context) {static int loops 5;char buf[100] {0};int ret;if(SIGRTMIN ! sig)return;/* 判断鼠标是否可读 */if (POLL_IN info-si_code) {ret read(fd, buf, sizeof(buf));if (0 ret)printf(鼠标: 成功读取%d个字节数据\n, ret);loops--;if (0 loops) {close(fd);exit(0);}} }int main(void) {struct sigaction act;int flag;/* 打开鼠标设备文件使能非阻塞 I/O */fd open(MOUSE, O_RDONLY | O_NONBLOCK);if (-1 fd) {perror(open error);exit(-1);}/* 使能异步 I/O */flag fcntl(fd, F_GETFL);flag | O_ASYNC;fcntl(fd, F_SETFL, flag);/* 设置异步 I/O 的所有者 */fcntl(fd, F_SETOWN, getpid());/* 指定实时信号 SIGRTMIN 作为异步 I/O 通知信号 */fcntl(fd, F_SETSIG, SIGRTMIN);/* 为实时信号 SIGRTMIN 注册信号处理函数 */act.sa_sigaction io_handler;act.sa_flags SA_SIGINFO;sigemptyset(act.sa_mask);sigaction(SIGRTMIN, act, NULL);for (;;)sleep(1); }5. 存储映射 I/O 存储映射 I/O 是一种基于内存区域的高级 I/O 操作它能将一个文件映射到进程地址空间中的一块内存区域中 当从这段内存中读数据时就相当于读文件中的数据read 操作将数据写入这段内存时则相当于将数据直接写入文件中write 操作这样就可以在不使用基本 I/O 操作函数 read() 和 write() 的情况下执行 I/O 操作 5.1 mmap() 和 munmap() 函数 mmap() 用于告诉内核将一个给定的文件映射到进程地址空间中的一块内存区域中 #include sys/mman.hvoid *mmap(void *addr, size_t length, int prot, int flags, int fd, off_t offset);addr用于指定映射到内存区域的起始地址 通常将其设置为 NULL这表示由系统选择该映射区的起始地址这是最常见的设置方式如果参数 addr 不为 NULL则表示由自己指定映射区的起始地址此函数的返回值是该映射区的起始地址 length指定映射长度表示将文件中的多大部分映射到内存区域中 以字节为单位如length1024 * 4表示将文件的 4K 字节大小映射到内存区域中参数 length 的值不能大于文件大小即文件被映射的部分不能超出文件 offset文件映射的偏移量 通常将其设置为 0表示从文件头部开始映射参数 offset 和参数 length 就确定了文件的起始位置和长度将文件的这部分映射到内存区域中 fd文件描述符指定要映射到内存区域中的文件prot指定了映射区的保护要求可取值如下 PROT_EXEC映射区可执行PROT_READ映射区可读PROT_WRITE映射区可写PROT_NONE映射区不可访问 对指定映射区的保护要求不能超过文件 open() 时的访问权限如文件是以只读权限方式打开的那么对映射区的不能指定为 PROT_WRITE flags可影响映射区的多种属性参数 flags 必须要指定以下两种标志之一 MAP_SHARED此标志指定当对映射区写入数据时数据会写入到文件中也就是会将写入到映射区中的数据更新到文件中并且允许其它进程共享MAP_PRIVATE此标志指定当对映射区写入数据时会创建映射文件的一个私人副本对映射区的任何操作都不会更新到文件中仅仅只是对文件副本进行读写。 返回值 成功情况下函数的返回值便是映射区的起始地址发生错误时返回 (void *)-1通常使用 MAP_FAILED 来表示并且会设置 errno 来指示错误原因 对于 mmap() 函数参数 addr 和 offset 在不为 NULL 和 0 的情况下addr 和 offset 的值通常被要求是系统页大小的整数倍可通过 sysconf() 函数获取页大小 通过 mmap() 将文件映射到进程地址空间中的一块内存区域中当不再需要时使用 munmap() 解除映射关系 被解除映射的区域对应的大小也必须是系统页大小的整数倍即使参数 length 并不等于系统页大小的整数倍当进程终止时也会自动解除映射如果没有显式调用 munmap()但调用 close() 关闭文件时并不会解除映射 #include sys/mman.hint munmap(void *addr, size_t length);示例使用存储映射 I/O 复制文件 #include stdio.h #include stdlib.h #include sys/types.h #include sys/stat.h #include fcntl.h #include unistd.h #include sys/mman.h #include string.hint main(int argc, char* argv[]) {int srcfd, dstfd;void *srcaddr;void *dstaddr;int ret;struct stat sbuf;if (argc ! 3) {fprintf(stderr, usage: %s srcfile dstfile\n, argv[0]);exit(-1);}/* 打开源文件 */srcfd open(argv[1], O_RDONLY);if (srcfd -1) {perror(open error);exit(-1);}/* 打开目标文件 */dstfd open(argv[2], O_RDWR | O_CREAT | O_TRUNC, 0664);if (dstfd -1) {perror(open error);ret -1;goto out1;}/* 获取源文件的大小 */fstat(srcfd, sbuf);/* 设置目标文件的大小 */ftruncate(dstfd, sbuf.st_size);/* 将源文件映射到内存区域中 */// 将参数 prot 指定为 PROT_READ表示对它的映射区会进行读取操作srcaddr mmap(NULL, sbuf.st_size, PROT_READ, MAP_SHARED, srcfd, 0);if (srcaddr MAP_FAILED) {perror(mmap error);ret -1;goto out2;}/* 将目标文件映射到内存区域中 */// 将参数 port 指定为 PROT_WRITE表示对它的映射区会进行写入操作dstaddr mmap(NULL, sbuf.st_size, PROT_WRITE, MAP_SHARED, dstfd, 0);if (dstaddr MAP_FAILED) {perror(mmap error);ret -1;goto out3;}/* 将源文件映射区中的内容复制到目标文件映射区中完成文件复制操作 */memcpy(dstaddr, srcaddr, sbuf.st_size);/* 程序退出前清理工作 */ out4:/* 解除目标文件映射 */munmap(dstaddr, sbuf.st_size); out3:/* 解除源文件映射 */munmap(srcaddr, sbuf.st_size); out2:/* 关闭目标文件 */close(dstfd); out1:/* 关闭源文件并退出 */close(srcfd);exit(ret); }$ touch srcfile $ vi srcfile $ cat srcfile Hello World! Hunan normal university.$ gcc copy.c -o copy $ ./copy ./srcfile ./dstfile $ cat dstfile Hello World! Hunan normal university.5.2 mprotect() 函数 使用系统调用 mprotect() 可以更改一个现有映射区的保护要求#include sys/mman.h// 参数 prot 的取值与 mmap() 函数一样mprotect() 函数会将指定地址范围的保护要求更改为参数 prot 所指定的类型 // 参数 addr 指定该地址范围的起始地址addr 的值必须是系统页大小的整数倍 // 参数 len 指定该地址范围的大小 int mprotect(void *addr, size_t len, int prot);5.3 msync() 函数 调用 write() 写入到磁盘文件中的数据并不会立马写入磁盘而是会先缓存在内核缓冲区中所以就会出现 write() 操作与磁盘操作并不同步也就是数据不同步对于存储 I/O 来说亦是如此写入到文件映射区中的数据不会立马刷新至磁盘设备中而是会在将数据写入到映射区之后的某个时刻将映射区中的数据写入磁盘中所以会导致映射区中的内容与磁盘文件中的内容不同步 可以调用 msync() 函数将映射区中的数据刷写、更新至磁盘文件中同步操作 #include sys/mman.hint msync(void *addr, size_t length, int flags);addr 和 length分别指定了需同步的内存区域的起始地址和大小flags应指定为 MS_ASYNC 和 MS_SYNC 两个标志之一除此之外还可以根据需求选择是否指定 MS_INVALIDATE 标志作为一个可选标志 MS_ASYNC以异步方式进行同步操作。调用 msync() 函数并不会等待数据完全写入磁盘之后才返回MS_SYNC以同步方式进行同步操作。调用 msync() 函数需等待数据全部写入磁盘之后才返回MS_INVALIDATE可选标志请求使同一文件的其它映射无效以便可以用刚写入的新值更新它们 5.4 普通 I/O 与存储映射 I/O 比较 普通 I/O 方式的缺点 普通 I/O 方式一般是通过调用 read() 和 write() 函数实现对文件读写使用 read() 和 write() 读写文件时函数经过层层调用才能最终操作文件效率会比较低使用标准 I/O库函数 fread()、fwrite()同样如此标准 I/O 就是对普通 I/O 的一种封装只有当数据量比较大时效率的影响才会比较明显如果数据量比较小则影响并不大使用普通的 I/O 方式还是非常方便的 存储映射 I/O 的优点 存储映射 I/O 的实质其实是共享 通过存储映射 I/O 将文件直接映射到应用程序地址空间中的一块内存区域中也就是映射区直接将磁盘文件直接与映射区关联起来不用调用 read()、write()系统调用直接对映射区进行读写操作即可操作磁盘上的文件而磁盘文件中的数据也可反应到映射区中这就是一种共享可以认为映射区就是应用层与内核层之间的共享内存 使用存储映射 I/O 减少了数据的复制操作效率比普通 I/O 高如执行一个文件复制操作来说对于普通 I/O 方式首先需要将源文件中的数据读取出来存放在一个应用层缓冲区中接着再将缓冲区中的数据写入到目标文件中而对于存储映射 I/O 来说由于源文件和目标文件都已映射到了应用层的内存区域中所以直接操作映射区来实现文件复制 存储映射 I/O 的不足 所映射的文件只能是固定大小因为文件所映射的区域已经在调用 mmap() 函数时通过 length 参数指定文件映射的内存区域的大小必须是系统页大小的整数倍使用存储映射 I/O 在进行大数据量操作时比较有效对于少量数据则使用普通 I/O 方式更方便 6. 文件锁 多个进程同时操作同一文件很容易导致文件中的数据发生混乱因为多个进程对文件进行 I/O 操作时容易产生竞争状态导致文件中的内容与预想的不一致 进程有时需要确保只有它自己能够对某一文件进行 I/O 操作在这段时间内不允许其它进程对该文件进行 I/O 操作 Linux 系统提供了文件锁机制来实现文件锁是用于对共享资源的访问进行保护的机制对文件上锁来避免多个进程同时操作同一文件时产生竞争状态 6.1 文件锁的分类 建议性锁 本质上是一种协议程序访问文件前先对文件上锁上锁成功后再访问文件在文件没有上锁的情况下直接访问文件也可以实现但这样的话建议性锁就没有起作用 强制性锁 如果进程对文件上了强制性锁其它进程在没有获取到文件锁的情况下是无法对文件进行访问的原因强制性锁会让内核检查每一个 I/O 操作如 read()、write()验证调用进程是否是该文件锁的拥有者如果不是将无法访问文件采取强制性锁对性能的影响很大每次进行读写操作都必须检查文件锁 6.2 flock() 函数加锁 使用 flock() 函数可以对文件加锁或者解锁但只能产生建议性锁 fd文件描述符指定需要加锁的文件operation指定了操作方式可以设置为以下值的其中一个 LOCK_SH在 fd 引用的文件上放置一把共享锁。所谓共享指的便是多个进程可以拥有对同一个文件的共享锁该共享锁可被多个进程同时拥有LOCK_EX在 fd 引用的文件上放置一把互斥锁。所谓互斥指的便是互斥锁只能同时被一个进程所拥有LOCK_UN解除文件锁定状态解锁、释放锁LOCK_NB表示以非阻塞方式获取锁。默认情况下调用 flock() 无法获取到文件锁时会阻塞直到其它进程释放锁为止如果不想程序被阻塞可以指定 LOCK_NB 标志如果无法获取到锁应立刻返回errno 设置为 EWOULDBLOCK通常与 LOCK_SH 或 LOCK_EX 一起使用通过位或运算符组合 对于 flock()同一个文件不会同时具有共享锁和互斥锁 #include sys/file.hint flock(int fd, int operation);示例 1使用 flock() 对文件加锁/解锁 // test1.c #include stdio.h #include stdlib.h #include sys/types.h #include sys/stat.h #include fcntl.h #include unistd.h #include sys/file.h #include signal.hstatic int fd -1; // 文件描述符/* 信号处理函数 */ static void sigint_handler(int sig) {if (sig ! SIGINT) {return;}/* 解锁 */flock(fd, LOCK_UN);close(fd);printf(进程 1: 文件已解锁!\n); }int main(int argc, char *argv[]) {if (argc ! 2) {fprintf(stderr, usage: %sfile\n, argv[0]);exit(-1);}/* 打开文件 */ fd open(argv[1], O_WRONLY);if (fd -1) {perror(open error);exit(-1);}/* 以非阻塞方式对文件加锁(互斥锁) */if (flock(fd, LOCK_EX | LOCK_NB)) {perror(进程 1: 文件加锁失败);exit(-1);}printf(进程 1: 文件加锁成功!\n);/* 为 SIGINT 信号注册处理函数 */// 当进程接收到 SIGINT 信号后会执行 sigint_handler() 函数// 在信号处理函数中对文件进行解锁然后终止进程signal(SIGINT, sigint_handler);for (;;) {sleep(1);} }示例 2未获取锁情况下读写文件 // test2.c #include stdio.h #include stdlib.h #include sys/types.h #include sys/stat.h #include fcntl.h #include unistd.h #include sys/file.h #include string.hint main(int argc, char *argv[]) {char buf[100] Hello World!;int fd;int len;if (argc ! 2) {fprintf(stderr, usage: %s file\n, argv[0]);exit(-1);}/* 可读可写方式打开文件 */fd open(argv[1], O_RDWR);if (fd -1) {perror(open error);exit(-1);}/* 以非阻塞方式对文件加锁(互斥锁) */if (flock(fd, LOCK_EX | LOCK_NB) -1) {perror(进程 2: 文件加锁失败);} else {printf(进程 2: 文件加锁成功!\n);}/* 写文件 */len strlen(buf);if (write(fd, buf, len) 0) {perror(write error);exit(-1);}printf(进程 2: 写入到文件的字符串%s\n, buf);/* 将文件读写位置移动到文件头 */if (lseek(fd, 0x0, SEEK_SET) 0) {perror(lseek error);exit(-1);}/* 读文件 */memset(buf, 0x0, sizeof(buf)); //清理 bufif (read(fd, buf, len) 0) {perror(read error);exit(-1);}printf(进程 2: 从文件读取的字符串%s\n, buf);/* 解锁、退出 */flock(fd, LOCK_UN);close(fd);exit(0); } $ gcc test1.c -o test1 $ gcc test2.c -o test2 $ touch infile# 首先执行 test1 应用程序将 infile 文件作为输入文件并将其放置在后台运行 $ ./test1 ./infile [1] 3234 进程 1: 文件加锁成功!# test1 会在后台运行由 ps 命令可查看到其 pid 为 3234 $ psPID TTY TIME CMD3068 pts/0 00:00:00 bash3234 pts/0 00:00:00 test13235 pts/0 00:00:00 ps# 接着执行 test2 应用程序传入相同的文件 infile # test2 进程对 infile 文件加锁失败原因在于锁已经被 test1 进程所持有 # 但是 test2 对文件的读写操作是没有问题的 $ ./test2 ./infile 进程 2: 文件加锁失败: Resource temporarily unavailable 进程 2: 写入到文件的字符串Hello World! 进程 2: 从文件读取的字符串Hello World!# 接着向 test1 进程发送一个 SIGIO 信号编号为 2让其对文件 infile 解锁 $ kill -2 3234 进程 1: 文件已解锁!# 接着再执行一次 test2 $ ./test2 ./infile # test2 成功对 infile 文件加锁读写也是没有问题的 $ ./test2 ./infile 进程 2: 文件加锁成功! 进程 2: 写入到文件的字符串Hello World! 进程 2: 从文件读取的字符串Hello World!关于 flock() 的几条规则 1. 同一进程对文件多次加锁不会导致死锁 新加的锁会替换旧的锁如先调用 flock() 对文件加共享锁然后再调用 flock() 对文件加互斥锁最终文件锁会由共享锁替换为互斥锁2. 文件关闭时会自动解锁 文件锁会在相应的文件描述符被关闭之后自动释放当一个进程终止时它所建立的锁将全部释放3. 一个进程不可以对另一个进程持有的文件锁进行解锁4. 由 fork() 创建的子进程不会继承父进程所创建的锁 若一个进程对文件加锁成功然后该进程调用 fork() 创建了子进程那么对父进程创建的锁而言子进程被视为另一个进程虽然子进程从父进程继承了其文件描述符但不能继承文件锁因为锁的作用就是阻止多个进程同时写同一个文件5. 当一个文件描述符被复制时使用 dup()、dup2() 或 fcntl() F_DUPFD 操作 这些通过复制得到的文件描述符和源文件描述符都会引用同一个文件锁使用这些文件描述符中的任何一个进行解锁都可以 flock(fd, LOCK_EX); // 加锁 new_fd dup(fd); flock(new_fd, LOCK_UN); // 解锁 // 如果不显示的调用一个解锁操作只有当所有文件描述符都被关闭之后锁才会被释放 // 如果不调用 flock(new_fd, LOCK_UN) 进行解锁只有当 fd 和 new_fd 都被关闭之后锁才会自动释放6.3 fcntl() 函数加锁 fcntl() 实现文件锁功能与 flock() 的区别 flock() 仅支持对整个文件进行加锁/ 解锁而 fcntl() 可以对文件的某个区域某部分内容进行加锁/解锁 可以精确到某一个字节数据flock() 仅支持建议性锁类型而 fcntl() 可支持建议性锁和强制性锁两种类型但一般不建议使用强制性锁 #include unistd.h #include fcntl.h// 与锁相关的 cmd 为 F_SETLK、F_SETLKW、F_GETLK第三个参数 flockptr 是一个 struct flock 结构体指针 int fcntl(int fd, int cmd, ... /* struct flock *flockptr */ );struct flock 结构体 struct flock {.../* 锁类型可设置为F_RDLCK 表示共享性质的读锁F_WRLCK 表示独占性质的写锁F_UNLCK 表示解锁一个区域 */short l_type; /* l_whence 和 l_start这两个变量用于指定要加锁或解锁区域的起始字节偏移量 */short l_whence; /* 插入 l_start 的位置: SEEK_SET, SEEK_CUR, SEEK_END */off_t l_start; /* Starting offset for lock */off_t l_len; /* 需要加锁或解锁区域的字节长度 */// 一个 pid指向一个进程表示该进程持有的锁能阻塞当前进程当 cmdF_GETLK 时有效pid_t l_pid;... };两种类型的锁F_RDLCK共享性读锁和 F_WRLCK独占性写锁 任意多个进程在一个给定的字节上可以有一把共享读锁但在一个给定字节上只能有一个进程有一把独占写锁当对文件的某一区域加读锁时调用进程必须对该文件有读权限如 open() 时 flags 参数指定了 O_RDONLY 或 O_RDWR当对文件的某一区域加写锁时调用进程必须对该文件有写权限如 open() 时 flags 参数指定了 O_WRONLY 或 O_RDWR 文件锁相关的三个 cmdF_SETLK、F_SETLKW 和 F_GETLK F_GETLK 一般很少用通常用于测试测试调用进程对文件加一把由参数 flockptr 指向的 struct flock 对象所描述的锁是否会加锁成功 F_SETLK 对文件添加由 flockptr 指向的 struct flock 对象所描述的锁如试图对文件的某一区域加读锁l_type 等于 F_RDLCK或写锁l_type 等于 F_WRLCK也可用于清除由 flockptr 指向的 struct flock 对象所描述的锁l_type 等于 F_UNLCK F_SETLKW 此命令是 F_SETLK 的阻塞版本如果所请求的读锁或写锁因另一个进程当前已经对所请求区域的某部分进行了加锁而导致请求失败那么调用进程将会进入阻塞状态只有当请求的锁可用时进程才会被唤醒 示例 1使用 fcntl() 对文件加锁/解锁 #include stdio.h #include stdlib.h #include sys/types.h #include sys/stat.h #include fcntl.h #include unistd.h #include string.hint main(int argc, char *argv[]) {struct flock lock {0};int fd -1;char buf[] Hello World!;/* 校验传参 */if (2 ! argc) {fprintf(stderr, usage: %s file\n, argv[0]);exit(-1);}/* 打开文件 */fd open(argv[1], O_WRONLY);if (-1 fd) {perror(open error);exit(-1);}/* 对文件加锁 */lock.l_type F_WRLCK; // 独占性写锁lock.l_whence SEEK_SET; // 文件头部lock.l_start 0; // 偏移量为 0lock.l_len 0; // l_len 设置为 0 表示对整个文件加锁if (-1 fcntl(fd, F_SETLK, lock)) {perror(加锁失败);exit(-1);}printf(对文件加锁成功!\n);/* 对文件进行写操作 */if (0 write(fd, buf, strlen(buf))) {perror(write error);exit(-1);}/* 解锁 */lock.l_type F_UNLCK;fcntl(fd, F_SETLK, lock);/* 退出 */close(fd);exit(0); }$ gcc fcntl.c -o fcntl $ touch testfile $ ./fcntl ./testfile 对文件加锁成功!示例 2使用 fcntl() 对文件不同区域进行加锁 一个进程可以对同一个文件的不同区域进行加锁当然这两个区域不能有重叠的情况如果两个区域出现了重叠如 100~200 字节区间和 150~250 字节区间150~200 就是它们的重叠部分一个进程对同一文件的相同区域不可能同时加两把锁新加的锁会把旧的锁替换掉 #include stdio.h #include stdlib.h #include sys/types.h #include sys/stat.h #include fcntl.h #include unistd.hint main(int argc, char *argv[]) {struct flock wr_lock {0};struct flock rd_lock {0};int fd -1;/* 校验传参 */if (2 ! argc) {fprintf(stderr, usage: %s file\n, argv[0]);exit(-1);}/* 打开文件 */fd open(argv[1], O_RDWR);if (-1 fd) {perror(open error);exit(-1);}/* 将文件大小截断为 1024 字节 */ftruncate(fd, 1024);/* 对 100~200 字节区间加写锁 */wr_lock.l_type F_WRLCK;wr_lock.l_whence SEEK_SET;wr_lock.l_start 100;wr_lock.l_len 100;if (-1 fcntl(fd, F_SETLK, wr_lock)) {perror(加写锁失败);exit(-1);}printf(加写锁成功!\n);/* 对 400~500 字节区间加读锁 */rd_lock.l_type F_RDLCK;rd_lock.l_whence SEEK_SET;rd_lock.l_start 400;rd_lock.l_len 100;if (-1 fcntl(fd, F_SETLK, rd_lock)) {perror(加读锁失败);exit(-1);}printf(加读锁成功!\n);/* 对文件进行 I/O 操作 */// ......// ....../* 解锁 */wr_lock.l_type F_UNLCK; // 写锁解锁fcntl(fd, F_SETLK, wr_lock);rd_lock.l_type F_UNLCK; // 读锁解锁fcntl(fd, F_SETLK, rd_lock);/* 退出 */close(fd);exit(0); }示例 3读锁的共享性测试 #include stdio.h #include stdlib.h #include sys/types.h #include sys/stat.h #include fcntl.h #include unistd.hint main(int argc, char *argv[]) {struct flock lock {0};int fd -1;/* 校验传参 */if (2 ! argc) {fprintf(stderr, usage: %s file\n, argv[0]);exit(-1);}/* 打开文件 */fd open(argv[1], O_RDWR);if (-1 fd) {perror(open error);exit(-1);}/* 将文件大小截断为 1024 字节 */ftruncate(fd, 1024);/* 对 400~500 字节区间加读锁 */lock.l_type F_RDLCK;lock.l_whence SEEK_SET;lock.l_start 400;lock.l_len 100;if (-1 fcntl(fd, F_SETLK, lock)) {perror(加读锁失败);exit(-1);}printf(加读锁成功!\n);for (;;)sleep(1); }# 程序加读锁之后会进入死循环进程一直在运行并持有读锁 # 多个进程对同一文件的相同区域都可以加读锁说明读锁是共享性的 $ ./read ./testfile [1] 2771 加读锁成功! $ ./read ./testfile [2] 2773 加读锁成功! $ ./read ./testfile [3] 2774 加读锁成功! ...示例 4写锁的独占性测试 #include stdio.h #include stdlib.h #include sys/types.h #include sys/stat.h #include fcntl.h #include unistd.hint main(int argc, char *argv[]) {struct flock lock {0};int fd -1; /* 校验传参 */if (2 ! argc) {fprintf(stderr, usage: %s file\n, argv[0]);exit(-1);} /* 打开文件 */fd open(argv[1], O_RDWR);if (-1 fd) {perror(open error);exit(-1);} /* 将文件大小截断为 1024 字节 */ftruncate(fd, 1024);/* 对 400~500 字节区间加写锁 */lock.l_type F_WRLCK;lock.l_whence SEEK_SET;lock.l_start 400;lock.l_len 100;if (-1 fcntl(fd, F_SETLK, lock)) {perror(加写锁失败);exit(-1);} printf(加写锁成功!\n);for (;;) sleep(1); }# 第一次启动的进程对文件加写锁之后后面再启动进程对同一文件的相同区域加写锁都会失败因此写锁是独占性的 $ ./write ./testfile2 [5] 2812 加写锁成功! $ ./write ./testfile2 [6] 2813 加写锁失败: Resource temporarily unavailable ...使用 fcntl() 创建锁的几条规则 文件关闭的时候会自动解锁一个进程不可以对另一个进程持有的文件锁进行解锁由 fork() 创建的子进程不会继承父进程所创建的锁当一个文件描述符被复制时譬如使用 dup()、dup2()或 fcntl()F_DUPFD 操作这些通过复制得到的文件描述符和源文件描述符都会引用同一个文件锁使用这些文件描述符中的任何一个进行解锁都可以 6.4 lockf() 函数加锁 lockf() 函数是一个库函数其内部基于 fcntl() 实现所以 lockf() 是对 fcntl 锁的一种封装
http://www.pierceye.com/news/146791/

相关文章:

  • 建设部网站 测绘规章pc网站手机网站
  • 建网站如何赚钱vs哪个版本做网站好
  • 新衡阳网站游戏软件开发公司简介
  • 湖南基础建设投资集团网站做体育最好的网站
  • 上海php网站开发公司wordpress 邮件认证
  • 教做香肠的网站张家港专业网站建设
  • 园林建设网站营销型网站的建站步骤是什么意思
  • 招聘求职网站html模板正规的创业商机网
  • 预付网站建设费会计处理哪里建网站好
  • 做免费网站艺术学院网站建设管理办法
  • 做网站贵吗手机网站wap
  • linux建立网站做网站的应该怎么发广告
  • wordpress使用端口百度seo排名软
  • 用英文字母做网站关键词个人网站的设计与实现专业论文图像处理工具
  • 重庆企业网站推广流程php网站开发技术训练心得
  • 汽车销售网站学校建网站
  • 两台电脑一台做服务器 网站潍坊专业网站建设多少钱
  • 青岛科技街网站建设安徽 网站开发
  • 黑糖不苦建设的网站wordpress获取文章图片不显示
  • 美食网站建设的功能免费做简历的网站
  • 网站建设公司谁管手机如何创建网站
  • 可以自己做网站优化吗最好用的wordpress主题
  • 瓜子二手车网站开发智慧团建注册登记入口
  • 青岛网站开发建设安阳市商祺网络有限责任公司
  • 自己怎么做装修网站网站建设设计岗位职责
  • php语言 网站建设投资2 3万小生意
  • 全美网站开发微转app是用网站做的吗
  • 禹州 什么团购网站做的好广州网站建设程序开发
  • 成都市微信网站建设公司专业app开发
  • 郑州网站建设hndream神木网站设计公司