linux中fd的几点理解——一切皆文件

linux中fd的几点理解——一切皆文件fd 全称是 filedescript 文件描述符 又称句柄

大家好,欢迎来到IT知识分享网。

fd 全称是 file descriptor,文件描述符,又称句柄。当我们在打开一个文件,创建一个 socket, 创建一个 epoll 时,返回值往往都用一个变量 int fd 来表示。

1 fd 是进程级别的资源,不是系统级的资源

(1)fd 是进程级别的资源,一个文件可以对应用户态的很多 fd

当我们打开一个文件时,比如 /home/test 文件,会返回一个 fd。这个 fd 和 /home/test 并不是唯一对应的。

如下图所示,一个文件可以在一个应用内部打开多次,两次返回的 fd 肯定是不一样的;一个文件也可以被不同的应用打开,两个应用内的 fd 可能相同,也可能不相同。也就是说,一个文件可以对应用户态的很多 fd。

linux中fd的几点理解——一切皆文件

如下代码所示,同一个文件可以在进程内打开多次,并且返回的 fd 是不一样的。

#include <stdio.h> #include <stdlib.h> #include <fcntl.h> #include <unistd.h> int open_file() { int fd; char filename[] = "/home/test"; fd = open(filename, O_RDONLY); if (fd == -1) { perror("open file failed"); return -1; } char buffer[16] = {'\0'}; int n = read(fd, buffer, 16); if (n > 0) { printf("read content: %s", buffer); } else if (n == -1) { perror("read file error"); } else { printf("EOF\n"); } return fd; } int main() { int fd1 = open_file(); int fd2 = open_file(); printf("fd1: %d, fd2: %d\n", fd1, fd2); sleep(30); close(fd1); close(fd2); return 0; } 

(2)内核的 struct inode 是系统资源,一个文件只有一个

既然用户态的 fd 和文件不是唯一对应的,那么在系统中,什么对象和一个文件是唯一对应的呢?

inode

在 linux 中,一个文件只有一个 inode,无论在用户态有多少个应用打开这个文件,无论打开多少次,在内核中只有一个 struct inode 用来表示一个文件。struct inode 包括的内容比较多,其中比较好理解的是 inode 号(每个文件有一个唯一的 id),文件创建的时间,最近一次访问的时间,最近一次修改的时间。

使用 stat 命令也可以查看一个文件的上述信息,如下图所示,stat 命令中显示了文件的 inode 号,以及最近访问时间,最近改动时间和创建时间。

linux中fd的几点理解——一切皆文件

(3)一个应用可以打开的文件个数有限制

一个应用可以打开的 fd 的个数是有限制的,使用 ulimit -a 可以看到,其中 open files 这一项就是限制一个应用能打开的 fd 的个数,我的系统中是 1024。ulimit -a 显示的是系统的很多资源限制,如果想单独查看打开的文件的个数的限制,可以使用 ulimit -n 进行查看。也可以在 ulimit -n 后边加一个参数,对这个限制进行修改,比如 ulimit -n 2000 设置之后,一个应用最多就可以打开 2000 个文件。

linux中fd的几点理解——一切皆文件

为了验证 fd 个数限制,我们是用下边的代码进行测试,代码中尝试打开 2000 次文件,并且打开失败时退出循环。

结果截图如下,可以看到打开 1021 次之后,就失败了,失败信息是 “Too many open files”。之所以打开了 1021 个,而不是 1024 个,这是因为每个应用默认就会使用掉 3 个 fd,0、1、2,分别是标准输入,标准输出,标准错误。

linux中fd的几点理解——一切皆文件

#include <stdio.h> #include <stdlib.h> #include <fcntl.h> #include <unistd.h> int open_file() { int fd; char filename[] = "/home/test"; fd = open(filename, O_RDONLY); if (fd == -1) { perror("open file failed"); return -1; } return fd; } int main() { for (int i = 0; i < 2000; i ++) { int fd = open_file(); printf("i: %d, fd: %d\n", i, fd); if (fd == -1) { break; } } sleep(30); return 0; } 

2 linux 一切皆文件的理解

一切皆文件,本人理解说的是,站在用户态使用系统资源的时候,都可以通过打开一个 fd,然后自己想要的操作都可以通过读写 fd 来完成。操作磁盘上的文件,本身就是文件;通信中使用的 socket,多路复用技术中的 epoll,也都是创建了一个 fd;进程间通信中的管道,共享内存,创建的时候也是返回一个 fd。

传统意义上的文件是指磁盘中的文件,比如一个文本文件,一个照片,一个可执行文件,一个动态库,这些都是存储在磁盘中的文件。一切皆文件,并不是说 socket,epoll 都是磁盘上的文件,而是使用文件的思想来完成对应的功能。以 socket 为例,创建一个 socket 也是返回一个 fd,可以对这个 fd 进行 read(), write() 来收发数据。

内核基础:

(1)struct file_operations

struct file_operations 定义在文件 include/linux/fs.h。结构体的详细定义如下图所示,这个结构体中定义了一些接口,不同的文件系统,比如磁盘文件,socket, epoll 需要实现自己的接口。这也体现了面向对象的思想,接口抽象,实现,多态。

read()

write()

poll()

获取内容

open()

打开

struct file_operations { struct module *owner; loff_t (*llseek) (struct file *, loff_t, int); ssize_t (*read) (struct file *, char __user *, size_t, loff_t *); ssize_t (*write) (struct file *, const char __user *, size_t, loff_t *); ssize_t (*read_iter) (struct kiocb *, struct iov_iter *); ssize_t (*write_iter) (struct kiocb *, struct iov_iter *); int (*iopoll)(struct kiocb *kiocb, bool spin); int (*iterate) (struct file *, struct dir_context *); int (*iterate_shared) (struct file *, struct dir_context *); __poll_t (*poll) (struct file *, struct poll_table_struct *); long (*unlocked_ioctl) (struct file *, unsigned int, unsigned long); long (*compat_ioctl) (struct file *, unsigned int, unsigned long); int (*mmap) (struct file *, struct vm_area_struct *); unsigned long mmap_supported_flags; int (*open) (struct inode *, struct file *); int (*flush) (struct file *, fl_owner_t id); int (*release) (struct inode *, struct file *); int (*fsync) (struct file *, loff_t, loff_t, int datasync); int (*fasync) (int, struct file *, int); int (*lock) (struct file *, int, struct file_lock *); ssize_t (*sendpage) (struct file *, struct page *, int, size_t, loff_t *, int); unsigned long (*get_unmapped_area)(struct file *, unsigned long, unsigned long, unsigned long, unsigned long); int (*check_flags)(int); int (*flock) (struct file *, int, struct file_lock *); ssize_t (*splice_write)(struct pipe_inode_info *, struct file *, loff_t *, size_t, unsigned int); ssize_t (*splice_read)(struct file *, loff_t *, struct pipe_inode_info *, size_t, unsigned int); int (*setlease)(struct file *, long, struct file_lock , void ); long (*fallocate)(struct file *file, int mode, loff_t offset, loff_t len); void (*show_fdinfo)(struct seq_file *m, struct file *f); #ifndef CONFIG_MMU unsigned (*mmap_capabilities)(struct file *); #endif ssize_t (*copy_file_range)(struct file *, loff_t, struct file *, loff_t, size_t, unsigned int); loff_t (*remap_file_range)(struct file *file_in, loff_t pos_in, struct file *file_out, loff_t pos_out, loff_t len, unsigned int remap_flags); int (*fadvise)(struct file *, loff_t, loff_t, int); bool may_pollfree; } __randomize_layout;

下面以 socket 为例,介绍 socket 对 struct file_operations 的实现。socket 实现的结构体是 socket_file_ops,我们重点关注 read_iter 和 write_iter,通过这两个函数可以收发数据。

static const struct file_operations socket_file_ops = { .owner = THIS_MODULE, .llseek = no_llseek, .read_iter = sock_read_iter, .write_iter = sock_write_iter, .poll = sock_poll, .unlocked_ioctl = sock_ioctl, #ifdef CONFIG_COMPAT .compat_ioctl = compat_sock_ioctl, #endif .mmap = sock_mmap, .release = sock_close, .fasync = sock_fasync, .sendpage = sock_sendpage, .splice_write = generic_splice_sendpage, .splice_read = sock_splice_read, .show_fdinfo = sock_show_fdinfo, };

以接收数据为例,子用户态通过调用 read() 来接收数据。

 // read() 系统调用定义如下,read() 中调用 ksys_read() // fs/read_write.c SYSCALL_DEFINE3(read, unsigned int, fd, char __user *, buf, size_t, count) { return ksys_read(fd, buf, count); } // ksys_read() 中调用了 vfs_read() // vfs 相当于把不同的文件系统的接口统一起来,在 vfs 层调用不同文件系统的实现 // 类似于面向对象中,创建一个子类对象可以赋值给一个声明为父类的变量 // 然后通过这个父类变量调用方法又可以调用到子类的方法 // fs/read_write.c ssize_t ksys_read(unsigned int fd, char __user *buf, size_t count) { ... ret = vfs_read(f.file, buf, count, ppos); ... } // vfs_read() 中首先判断有没有实现 read() 方法,如果实现了则调用 read() // 如果没有实现 read() 方法则判断有没有实现 read_iter 方法,如果实现了则调用 new_sync_read() // socket_file_ops 中实现了 read_iter() 方法,没有实现 read() 方法 // fs/read_write.c ssize_t vfs_read(struct file *file, char __user *buf, size_t count, loff_t *pos) { ... if (count > MAX_RW_COUNT) count = MAX_RW_COUNT; if (file->f_op->read) ret = file->f_op->read(file, buf, count, pos); else if (file->f_op->read_iter) ret = new_sync_read(file, buf, count, pos); ... return ret; } 最终调用到了 sock_read_iter() 来接收数据,sock_read_iter() 中调用了 sock_recvmsg()。

对于 tcp 来说,也可以通过 recv() 和 send() 来收发数据。通过 recv() 来接收数据,没有经过 vfs 层,最终也是调用到 sock_recvmsg() 来接收数据。

如下是通过 read(), write() 收发数据的例子。

server:

#include <stdlib.h> #include <stdio.h> #include <errno.h> #include <string.h> #include <netdb.h> #include <sys/types.h> #include <netinet/in.h> #include <sys/socket.h> #include <netinet/tcp.h> #include <unistd.h> #define MAXLINE 1024 int main(int argc, char *argv[]) { // 1. 创建一个监听 socket int listenfd = socket(AF_INET, SOCK_STREAM, 0); if(listenfd < 0) { fprintf(stderr, "socket error : %s\n", strerror(errno)); return -1; } // 2. 初始化服务器地址和端口 struct sockaddr_in server_addr; bzero(&server_addr, sizeof(struct sockaddr_in)); server_addr.sin_family = AF_INET; server_addr.sin_addr.s_addr = htonl(INADDR_ANY); server_addr.sin_port = htons(8888); // 3. 绑定地址+端口 if(bind(listenfd, (struct sockaddr *)(&server_addr), sizeof(struct sockaddr)) < 0) { fprintf(stderr,"bind error:%s\n", strerror(errno)); return -1; } printf("begin listen....\n"); // 4. 开始监听 if(listen(listenfd, 128)) { fprintf(stderr, "listen error:%s\n\a", strerror(errno)); exit(1); } // 5. 获取已连接的socket struct sockaddr_in client_addr; socklen_t client_addrlen = sizeof(client_addr); int clientfd = accept(listenfd, (struct sockaddr *)&client_addr, &client_addrlen); if(clientfd < 0) { fprintf(stderr, "accept error:%s\n\a", strerror(errno)); exit(1); } printf("accept success\n"); char message[MAXLINE] = {0}; while(1) { //6. 读取客户端发送的数据 int n = read(clientfd, message, MAXLINE); if(n < 0) { // 读取错误 fprintf(stderr, "read error:%s\n\a", strerror(errno)); break; } else if(n == 0) { // 返回 0 ,代表读到 FIN 报文 fprintf(stderr, "client closed \n"); sleep(1); close(clientfd); // 没有数据要发送,立马关闭连接 break; } message[n] = 0; printf("received %d bytes: %s\n", n, message); } close(listenfd); return 0; } 

client:

#include <stdlib.h> #include <stdio.h> #include <errno.h> #include <string.h> #include <netdb.h> #include <sys/types.h> #include <netinet/in.h> #include <sys/socket.h> int main(int argc, char *argv[]) { // 1. 创建一个监听 socket int connectfd = socket(AF_INET, SOCK_STREAM, 0); if(connectfd < 0) { fprintf(stderr, "socket error : %s\n", strerror(errno)); return -1; } // 2. 初始化服务器地址和端口 struct sockaddr_in server_addr; bzero(&server_addr, sizeof(struct sockaddr_in)); server_addr.sin_family = AF_INET; server_addr.sin_addr.s_addr = inet_addr("127.0.0.1"); server_addr.sin_port = htons(8888); // 3. 连接服务器 if(connect(connectfd, (struct sockaddr *)(&server_addr), sizeof(server_addr)) < 0) { fprintf(stderr,"connect error:%s\n", strerror(errno)); return -1; } printf("connect success\n"); char sendline[64] = "hello, i am xiaolin"; //4. 发送数据 // int ret = send(connectfd, sendline, strlen(sendline), 0); int ret = write(connectfd, sendline, strlen(sendline)); if(ret != strlen(sendline)) { fprintf(stderr,"send data error:%s\n", strerror(errno)); return -1; } printf("already send %d bytes\n", ret); sleep(1); //5. 关闭连接 close(connectfd); return 0; } 

3 fd table

fd 代表着打开的文件,是进程级别的资源。在 linux 内核中,使用 struct task_struct 来表示一个进程,在 struct task_struct 中有一个成员是 struct files_struct *files,这个成员管理着应用打开的文件。

struct task_struct { ... /* Open file information: */ struct files_struct *files; ... } struct fdtable { unsigned int max_fds; struct file __rcu fd; /* current fd array */ unsigned long *close_on_exec; unsigned long *open_fds; unsigned long *full_fds_bits; struct rcu_head rcu; };

最终,fd 是保存在了 struct fdtable 中的 struct file __rcu fd,是一个数组,如下图所示,数组的下标是 fd,元素是一个 struct file *。所以,fd 和 struct file 都可以表示一个打开的文件,属于进程级资源。

linux中fd的几点理解——一切皆文件

struct file 中有一个成员为 const struct file_operations   *f_op,该成员中记录着 fd 的操作集,比如如果是通过 socket 创建的 fd,那么对应的就是 socket_file_ops 中的操作集。当系统调用 read(), write() 操作 fd 时,内核首先会通过 fd 在 fd table 中找到对应的 struct file,找到 struct file 就找到了操作集,然后句可以进行操作了。

4 fd 在进程间的传递 

fd 表示打开的文件,是进程级的资源,一个进程可以重复打开同一个文件,每次的 fd 是不同的,不同的进程也可以打开相同的文件,fd 可能相同,也可能不同。

如果一个进程 A 想访问另一个进程 B 打开的文件,如果进程 B 中的 fd 是 10,那么是不是进程 B 把 10 直接传递给 A,A 就可以通过 read() 或者 write() 读写这个文件了呢。这样是无法工作的。fd 10 在 A 中仅仅表示一个整数而已,并不代表一个打开的文件。想想也能理解,进程 B 在打开一个文件,返回 fd 10 的时候,不仅仅是用户态拿到了一个文件描述符 10,同时在内核也维护着很多的内容,进程 A 只使用一个整数 10,并没有在内核维护对应的内容,这样通过 read(), write() 把 10 传递给内核的时候,内核并不认识这个 fd。

fd 在进程之间也是可以传递的,通过 unix socket 和 cmsg 来实现。进程 B 将 fd 10 传递给进程 A 之后,在 A 中并不一定也是 10,也可能是其它数,但是它们都指向同一个文件。

unix socket 和 cmsg 中的 SCM_RIGHTS 专门用来在进程之间传递 fd。

下边是传递 fd 的代码,server 端代码和 client 端代码。编译之后,先运行 server 再运行 client,可以看到 server 写的内容,可以在 client 端读取到。

server 端,发送 fd:

#include <errno.h> #include <fcntl.h> #include <stddef.h> #include <stdio.h> #include <string.h> #include <sys/socket.h> #include <sys/stat.h> #include <sys/types.h> #include <sys/un.h> #include <unistd.h> #define SOCKET_PATH "./unix_socket_fd_transmit" int create_unix_server_and_accept_connection() { int server_fd; int connection_fd; int result; struct sockaddr_un sun; server_fd = socket(PF_UNIX, SOCK_STREAM, 0); printf("server fd: %d\n", server_fd); unlink(SOCKET_PATH); memset(&sun, 0, sizeof(sun)); sun.sun_family = AF_UNIX; strcpy(sun.sun_path, SOCKET_PATH); int len = offsetof(struct sockaddr_un, sun_path) + strlen(sun.sun_path) + 1; if (bind(server_fd, (struct sockaddr *)&sun, len) != 0) { printf("bind error\n"); return -1; } listen(server_fd, 8); struct sockaddr_un client_un; int un_len = sizeof(client_un); connection_fd = accept(server_fd, (struct sockaddr *)&client_un, &un_len); return connection_fd; } void send_fd(int socket_fd, int fd_to_be_send) { struct msghdr msg; struct cmsghdr *cmsg = NULL; int io[3] = {0, 1, 2}; char buf[CMSG_SPACE(sizeof(io))]; struct iovec iov; int dummy; memset(&msg, 0, sizeof(struct msghdr)); iov.iov_base = &dummy; iov.iov_len = 1; msg.msg_iov = &iov; msg.msg_iovlen = 1; msg.msg_control = buf; msg.msg_controllen = sizeof(buf); cmsg = CMSG_FIRSTHDR(&msg); cmsg->cmsg_len = CMSG_LEN(sizeof(fd_to_be_send)); cmsg->cmsg_level = SOL_SOCKET; cmsg->cmsg_type = SCM_RIGHTS; memcpy(CMSG_DATA(cmsg), &fd_to_be_send, sizeof(fd_to_be_send)); msg.msg_controllen = cmsg->cmsg_len; int size = sendmsg(socket_fd, &msg, 0); return; } int main(int argc, char const *argv[]) { int connection_fd = create_unix_server_and_accept_connection(); int fd_to_be_send = open("./test", O_RDWR | O_CREAT); char buf[] = "test transmit fd by unix socket between process"; write(fd_to_be_send, buf, sizeof(buf) + 1); printf("sender seek cur: %ld, ", lseek(fd_to_be_send, 0, SEEK_CUR)); printf("connection fd: %d, fd to be send: %d\n", connection_fd, fd_to_be_send); send_fd(connection_fd, fd_to_be_send); sleep(100); return 0; } 

 client 端,接收 fd:

#include <errno.h> #include <stdio.h> #include <string.h> #include <sys/socket.h> #include <sys/un.h> #include <unistd.h> #define SOCKET_PATH "./unix_socket_fd_transmit" int create_unix_connection() { int fd; int result; struct sockaddr_un sun; fd = socket(PF_UNIX, SOCK_STREAM, 0); sun.sun_family = AF_UNIX; strncpy(sun.sun_path, SOCKET_PATH, sizeof(sun.sun_path) - 1); connect(fd, (struct sockaddr *)&sun, sizeof(sun)); return fd; } void receive_fd(int socket_fd) { int dummy = 0; int received_fd; struct iovec iov; iov.iov_base = &dummy; iov.iov_len = 1; char buf[CMSG_SPACE(sizeof(received_fd))]; struct msghdr msg; memset(&msg, 0, sizeof(msg)); msg.msg_iov = &iov; msg.msg_iovlen = 1; msg.msg_control = buf; msg.msg_controllen = sizeof(buf); struct cmsghdr *cmsg; cmsg = CMSG_FIRSTHDR(&msg); cmsg->cmsg_len = CMSG_LEN(sizeof(received_fd)); cmsg->cmsg_level = SOL_SOCKET; cmsg->cmsg_type = SCM_RIGHTS; memcpy(CMSG_DATA(cmsg), &received_fd, sizeof(received_fd)); int size = recvmsg(socket_fd, &msg, 0); memcpy(&received_fd, CMSG_DATA(cmsg), sizeof(received_fd)); printf("received fd: %d\n", received_fd); printf("receiver seek cur: %ld\n", lseek(received_fd, 0, SEEK_CUR)); lseek(received_fd, 0, SEEK_SET); char receive_buf[128] = {'\0'}; int read_rc = read(received_fd, receive_buf, 128); printf("content in received fd: %s, read_rc: %d\n", receive_buf, read_rc); } int main(int argc, char const *argv[]) { int socket_fd; int fds[3]; socket_fd = create_unix_connection(); receive_fd(socket_fd); return 0; } 

5 对一个文件进行并发写

测试方案

内容会不会错乱

如何保证原子性

多个线程写一个 fd

不会

多线程写多个 fd,多个 fd 指向同一个文件

加锁

多进程写多个 fd,多个 fd 指向同一个文件

文件锁

(1)多线程写同一个 fd

如下代码,创建了两个线程,文件 test 打开一次,两个线程同时往 fd 中写数据。程序执行完毕之后,查看 test 中的内容,没有发现数据错乱的情况。

#include <stdio.h> #include <pthread.h> #include <unistd.h> #include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> int fd; void* do_write(void* arg) { char* data = (char*)arg; for (int i = 0; i < 100; i++) { write(fd, data, strlen(data)); } } int main() { fd = open("test", O_WRONLY | O_CREAT | O_TRUNC, 0644); if (fd == -1) { perror("open"); return 1; } pthread_t thread1; pthread_t thread2; char* data = "aaaa aaaa aaaa aaaa aaaa.\n"; char* data1 = "bbbb bbbb bbbb bbbb bbbb.\n"; pthread_create(&thread1, NULL, do_write, (void*)data); pthread_create(&thread2, NULL, do_write, (void*)data1); pthread_join(thread1, NULL); pthread_join(thread2, NULL); close(fd); return 0; } 

(2)多个线程写多个 fd

如下代码,test 文件打开两次,返回 fd1 和 fd2,两个线程分别向 fd1 和 fd2 中写数据。在不加锁的情况下,会出现内容的覆盖。两个线程共写了 200 行数据,文件中实际保存的数据少于 200 行。

如果把 do_write() 中的锁打开,那么就不会发生覆盖。

#include <stdio.h> #include <pthread.h> #include <unistd.h> #include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> int fd1; int fd2; pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER; void* do_write(void* arg) { char* data = (char*)arg; int fd = 0; if (data[0] == 'a') { fd = fd1; } else { fd = fd2; } printf("fd %d\n", fd); for (int i = 0; i < 100; i++) { // pthread_mutex_lock(&mutex); lseek(fd, 0, SEEK_END); write(fd, data, strlen(data)); // pthread_mutex_unlock(&mutex); } } int main() { fd1 = open("test", O_WRONLY | O_CREAT | O_TRUNC, 0644); if (fd1 == -1) { perror("open"); return 1; } fd2 = open("test", O_WRONLY | O_CREAT | O_TRUNC, 0644); if (fd2 == -1) { perror("open"); return 1; } pthread_t thread1, thread2; char* data1 = "aaaa aaaa aaaa aaaa aaaa.\n"; char* data2 = "bbbb bbbb bbbb bbbb.\n"; pthread_create(&thread1, NULL, do_write, (void*)data1); pthread_create(&thread2, NULL, do_write, (void*)data2); pthread_join(thread1, NULL); pthread_join(thread2, NULL); close(fd1); close(fd2); return 0; } 

(3)多进程同时写一个文件

一个进程内的多个线程同时写一个文件,就会出现数据的覆盖和错乱,多进程同时写一个文件,当然也会出现数据的覆盖和错乱。

进程内的多线程之间可以使用自旋锁或者 mutex 进行同步,多进程之间对一个文件进行操作时,可以通过文件锁来实现同步。

文件锁用一个结构体来实现,struct flock,各成员如下:

struct flock { short l_type; /* Type of lock: F_RDLCK, F_WRLCK, or F_UNLCK */ short l_whence; /* How to interpret l_start: SEEK_SET, SEEK_CUR, or SEEK_END */ off_t l_start; /* Starting offset for lock */ off_t l_len; /* Number of bytes to lock */ pid_t l_pid; /* PID of process blocking our lock (F_GETLK only) */ };

l_whence, l_start, l_len 3 个成员共同决定了文件锁定的范围。

l_whence 取值有 3 个,分别是 SEEK_SET,SEEK_CUR, SEEK_END,分别表示文件的开头,当前位置,文件的结尾。

l_start 相对于 l_whence 的偏移

l_len 保护的长度

下边代码中 l_whence 取值 SEEK_SET,l_start 取值 0, l_len 取值 0,表示对文件的所有内容进行保护。如何验证 flock 是否生效呢,我们可以改动一下下边的代码,获取到锁之后不释放锁,然后起动两个进程,可以看到其中一个进程获取到锁之后可以一直向文件写数据,但是另外一个进程一直在等锁。

#include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <fcntl.h> int main() { const char* filename = "test"; int fd = open(filename, O_WRONLY | O_CREAT | O_APPEND, 0666); if (fd == -1) { perror("Error opening file"); exit(EXIT_FAILURE); } struct flock fl; fl.l_type = F_WRLCK; fl.l_whence = SEEK_SET; fl.l_start = 0; fl.l_len = 0; for (int i = 0; i < 1000; i++) { printf("1111 before get lock\n"); if (fcntl(fd, F_SETLKW, &fl) == -1) { perror("Error locking file"); close(fd); exit(EXIT_FAILURE); } printf("1111 after get lock\n"); const char* message = "aaaa aaaa aaaa aaaa\n"; if (write(fd, message, strlen(message)) == -1) { perror("Error writing to file"); } sleep(1); printf("1111 before unlock\n"); fl.l_type = F_UNLCK; if (fcntl(fd, F_SETLK, &fl) == -1) { perror("Error unlocking file"); } printf("1111 after unlock\n"); } close(fd); return 0; } 

免责声明:本站所有文章内容,图片,视频等均是来源于用户投稿和互联网及文摘转载整编而成,不代表本站观点,不承担相关法律责任。其著作权各归其原作者或其出版社所有。如发现本站有涉嫌抄袭侵权/违法违规的内容,侵犯到您的权益,请在线联系站长,一经查实,本站将立刻删除。 本文来自网络,若有侵权,请联系删除,如若转载,请注明出处:https://haidsoft.com/137987.html

(0)
上一篇 2025-06-16 15:20
下一篇 2025-06-16 15:26

相关推荐

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

关注微信