linux系统编程(二)

1、fcntl

cpp 复制代码
#include <unistd.h>
int fcntl(int fd, int cmd, ...)

fcntl用于控制文件描述符,该系统调用有很多功能,功能用cmd来控制,fcntl后面的参数根据cmd来填充。

我们常用的cmd有:

  • F_GETFL:获取文件状态标志,所谓文件状态标志指的是文件在打开后的各种行为和状态的,比如说O_APPEND表示写入操作会将数据追加到文件末尾,O_NONBLOCK表示文件操作(如读取或写入)处于非阻塞模式。O_CREAT是一个用于文件创建的一次性标志,一旦文件创建成功,它就不再是文件状态的一部分
  • F_SETFL:修改文件状态标志,要注意的是允许修改的标志有O_APPEND、O_NONBLOCK、O_NOATIME、O_ASYNC 和 O_DIRECT,不能用于改变文件的打开模式(从只读改为读写)!!!
  • F_DUPFD:复制文件描述符,第三个参数为指定拷贝的文件描述符

我们在判断F_GETFL,要用

cpp 复制代码
if ((flags & O_RDONLY) == O_RDONLY)

如果只是获取读写权限可以用

cpp 复制代码
int accessMode = flags & O_ACCMODE;
cpp 复制代码
#include <unistd.h>
#include <fcntl.h>
#include <stdio.h>
#include <errno.h>
#include <stdlib.h>
#include <string.h>

int main(int argc, char **argv) {
	int fd1 = -1;
	fd1 = open("fcntl.txt", O_RDWR);
	if(fd1 < 0) {
		printf("open file failed, error msg: %s\n", strerror(errno));
		exit(1);
	}
	
	int flags = 0;
 	flags = fcntl(fd1, F_GETFL);
	if(flags == -1) {
		printf("fcntl F_GETFD fail, error msg: %s\n", strerror(errno));
		exit(1);
	}
	printf("flags: %x\n", flags);
	int accessMode = flags & O_ACCMODE;
	switch (accessMode) {
		case 0:
			printf("access mode is O_RDONLY\n");
			break;
		case 1:
			printf("access mode is O_WRONLY\n");
			break;
		case 2:
			printf("access mode is O_RDWR\n");
			break;
		default:
			printf("access mode is %x\n", accessMode);
			break;
	}
	if ((flags & O_RDONLY) == O_RDONLY) {
		printf("flag has O_RDONLY\n");	
	}
	if ((flags & O_RDWR) == O_RDWR) {
		printf("flag has O_RDWR\n");	
	}
	if ((flags & O_APPEND) ==  O_APPEND) {
		printf("flag has O_APPEND\n");	
	}
	/*
	if (flags & O_CREAT) {
		printf("flag has O_CREAT\n");	
	}*/
	const char *s = "today is sunday, happy :>\n";
	ssize_t written = write(fd1, s, strlen(s));
	if(written < 0) {
		printf("write fail, written: %ld, error msg: %s\n", written, strerror(errno));
	} else {
		printf("written: %ld\n", written);
	}

	mode_t mode = O_APPEND;
	flags = fcntl(fd1, F_SETFL, mode);
	if(flags == -1) {
		printf("fcntl F_SETFL fail\n");
	}

	written = write(fd1, s, strlen(s));
	if(written < 0) {
		printf("write fail, written: %ld, error msg: %s\n", written, strerror(errno));
	} else {
		printf("written: %ld\n", written);
	}

	int fd2 = fcntl(fd1, F_DUPFD);
	printf("fd1:%d, fd2:%d\n", fd1, fd2);

	const char *s2 = "hello, i am fd2\n";
	written = write(fd2, s2, strlen(s2));
	if(written < 0) {
		printf("write fail, written: %ld, error msg: %s\n", written, strerror(errno));
	} else {
		printf("written: %ld\n", written);
	}
	
    off_t off = lseek(fd2, 0, SEEK_SET);
	off = lseek(fd1, 0, SEEK_CUR);
	printf("fd1 file pointer is %ld\n", off);

	return 0;
}

执行得到的结果是:

cpp 复制代码
flags: 8002
access mode is O_RDWR
flag has O_RDONLY
flag has O_RDWR
written: 26
written: 26
fd1:3, fd2:4
written: 16
fd1 file pointer is 0

2、dup

cpp 复制代码
#include <unistd.h>
int dup(int oldfd);

dup用于复制一个打开的文件描述符,它会返回最小的未使用的文件描述符。

默认情况下STDIN_FILENO(0)、STDOUT_FILENO(1)、STDERR_FILENO(2),这三个文件描述符是始终打开的。

cpp 复制代码
#include <unistd.h>
#include <stdio.h>

#define MAX_SIZE 1024

int main(int argc, char **argv) {
	int fd = -1;
	fd = dup(STDIN_FILENO);
	printf("fd: %d\n", fd);
	
	char buf[MAX_SIZE];
	ssize_t size = 0;
	while((size = read(fd, buf, MAX_SIZE-1)) > 0) {
		buf[size] = '\0';
		printf("read size:%ld -> %s", size, buf);
	}

	close(fd);
	return 0;
}

dup总是返回最小的未使用的文件描述符,如果我们想要返回指定的文件描述符,可以用dup2。dup2可以看作是close和dup的合成。

cpp 复制代码
#include <unistd.h>
int dup2(int oldfd, int newfd);

dup2会先关闭newfd,然后执行复制。这是一个危险行为,安全的做法是,调用dup2前,如果newfd已经打开,显式调用close将其关闭。

cpp 复制代码
#include <unistd.h>
#include <stdio.h>
#include <fcntl.h>
#include <errno.h>
#include <stdlib.h>
#include <string.h>

#define MAX_SIZE 1024

int main(int argc, char **argv) {
	int fd = -1;
	mode_t mode = S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH; 
	fd = open("dup2.txt", O_RDWR | O_CREAT | O_APPEND, mode);
	printf("fd: %d\n", fd);
	
	if(dup2(fd, STDOUT_FILENO) < 0) {
		printf("dup2 fail! error msg: %s\n", strerror(errno));
		exit(1);
	}
	
	printf("today is sunday, happy!\n");

	close(fd);
	return 0;
}

dup2在这个例子中作重定向的作用,虽然标准输出被关闭了,但是printf还是尝试向标准输出写数据,数据就会被写到文件中了。

当然我们也可以重定向标准输入,重定向标准输入意思就是从输入读数据时,实际上是从一个文件读的数据。

3、pread、pwrite

之前使用的系统调用read、write,用它们读写完数据之后,文件偏移量都会发生变动。有的时候我们只想临时向某个位置插入,这时候有两种选择:

  • 先执行lseek,将偏移量移动到指定位置,读写数据,再执行lseek恢复文件偏移量
  • pread、pwrite,它会帮我们完成两次lseek的动作。
cpp 复制代码
#include <unistd.h>

ssize_t pread(int fd, void *buf, size_t count, off_t offset);
ssize_t pwrite(int fd, void *buf, size_t count, off_t offset);

以下是代码示例:

cpp 复制代码
#include <unistd.h>
#include <stdio.h>
#include <string.h>
#include <errno.h>
#include <fcntl.h>
#include <stdlib.h>

#define MAX_SIZE 512

int main(int argc, char **argv) {
        int fd = -1;
        mode_t mode = S_IWUSR | S_IRUSR | S_IWGRP | S_IRGRP | S_IROTH;
        fd = open("pread.txt", O_RDWR | O_CREAT, 0664 /*mode*/);
        if(fd < 0) {
                printf("open / create file fail, error msg: %s\n", strerror(errno));
                exit(1);
        } else {
                printf("open / create file success!\n");
        }

        const char *s =  "today is Monday, sunny, cold! be happy, work hard, enjoy work and easy work!\n";

        ssize_t written = 0;
        written = write(fd, s, strlen(s));
        if(written < strlen(s)) {
                printf("write fail, written: %ld\n", written);
                exit(1);
        }

        off_t offset = lseek(fd, 0, SEEK_CUR);
        printf("current offset: %ld\n", offset);

        char buf[MAX_SIZE];
        ssize_t readlen = 0;
        int len = 6;
        readlen = pread(fd, buf, len, 9);
        if(readlen < len) {
                printf("pread error, readlen:%ld\n", readlen);
                exit(1);
        } else {
                printf("pread OK!\n");
                buf[len] = '\0';
                offset = lseek(fd, 0, SEEK_CUR);
                printf("current offset:%ld, read word: %s\n", offset,  buf);
        }

        const char *s1 = "Android is hard! ";

        written = pwrite(fd, s1, strlen(s1), 16);
        if(written < strlen(s1)) {
                printf("pwrite fail, written: %ld", written);
                exit(1);
        } else {
                printf("pwrite OK!\n");
                offset = lseek(fd, 0, SEEK_CUR);
                printf("current offset:%ld, written: %ld\n", offset, written);
        }

        lseek(fd, 0, SEEK_SET);
        while((readlen = read(fd, buf, MAX_SIZE - 1)) > 0) {
                buf[readlen] = '\0';
                printf("readlen:%ld -> %s\n", readlen, buf);
        }

        close(fd);

        return 0;
}

输出结果如下:

cpp 复制代码
open / create file success!
current offset: 77
pread OK!
current offset:77, read word: Monday
pwrite OK!
current offset:77, written: 17
readlen:77 -> today is Monday,Android is hard! happy, work hard, enjoy work and easy work!

4、readv、writev

之前了解的read、write、pread、pwrite都是一次只能写入一个缓冲区,或者一次读取一个缓冲区。linux还提供了readv和writev,可以一次将数据读取到多个缓冲区,一次将多个缓冲区写到文件中。

cpp 复制代码
#include <sys/uio.h>

ssize_t readv(int fd, const struct iovec *iov, int iovcnt);
ssize_t writev(int fd, const struct iovec *iov, int iovcnt);

struct iovec {
    void *iov_base;
    size_t iov_len;
}

iovcnt表示iov数组中有几个元素,每个iovec都会有一个地址,然后有对应的数据长度。

writev用于将多个非连续缓冲区中的数据原子性地写入文件描述符,它的使用场景是把固定格式的数据组合到一起发送出去,例如有固定格式的网络包(数据头+数据)、写日志文件等(时间+数据)。

以下是writev的一个简单示例:

cpp 复制代码
#include <unistd.h>
#include <fcntl.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>
#include <time.h>
#include <stdio.h>
#include <sys/uio.h>

#define MAX_SIZE 512

int main(int argc, char **argv) {
        int fd = -1;
        fd = open("writev.txt" , O_RDWR | O_CREAT, 0644);
        if(fd < 0) {
                printf("open file failed, error msg: %s\n", strerror(errno));
                exit(1);
        }

        struct iovec iov[2];
        time_t t;
        const char *s = "hello writev!\n";
        ssize_t written = 0;
        char buf[MAX_SIZE];
        for(int i = 0; i < 7; i++) {
                t = time(NULL);
                char *tmp = ctime(&t);
                memcpy(buf, tmp, strlen(tmp));
                buf[strlen(tmp) - 1] = ' ';     // remove '\n'
                iov[0].iov_base = buf;
                iov[0].iov_len = strlen(tmp);
                iov[1].iov_base = s;
                iov[1].iov_len = strlen(s);
                written = writev(fd, iov, 2);
                if(written !=  (iov[0].iov_len + iov[1].iov_len)) {
                        printf("writev fail, written: %ld, error msg: %s\n", written, strerror(errno));
                        exit(1);
                } else {
                        printf("writev success! written: %ld\n", written);
                }
                sleep(1);
        }

        off_t offset = lseek(fd, 0, SEEK_CUR);
        printf("current offset: %ld\n", offset);

        ssize_t readlen = 0;
        lseek(fd, 0, SEEK_SET);
        while((readlen = read(fd, buf, MAX_SIZE-1)) >  0) {
                buf[readlen] = '\0';
                printf("len: %ld -> %s\n", readlen, buf);
        }

        close(fd);

        return 0;
}

readv用于从文件描述符中读取数据到多个非连续的缓冲区中,它的使用场景主要是把固定格式的数据读到不同的缓冲区中,比如说网络包,固定格式的文件。

以下是一个readv示例:

cpp 复制代码
#include <unistd.h>
#include <stdio.h>
#include <sys/uio.h>
#include <string.h>
#include <errno.h>
#include <stdlib.h>
#include <fcntl.h>

#define TIME_LEN 25
#define STR_LEN  14
#define MAX_LEN  50

int main(int argc, char **argv) {
        int fd = -1;
        fd = open("writev.txt", O_RDONLY);
        if(fd < 0) {
                printf("open file failed, error msg: %s\n", strerror(errno));
                exit(1);
        }
        // "Mon Dec  9 13:29:17 2024 hello writev!\n"
        char bufTime[MAX_LEN];
        char bufStr[MAX_LEN];
        struct iovec iov[2];
        iov[0].iov_base = bufTime;
        iov[0].iov_len = TIME_LEN;
        iov[1].iov_base = bufStr;
        iov[1].iov_len = STR_LEN;

        ssize_t rd = 0;
        while((rd = readv(fd, iov, 2)) > 0) {
                bufTime[TIME_LEN] = '\0';
                bufStr[STR_LEN] = '\0';
                printf("readv OK, rd len: %ld\n"
                                "-> time: %s\n"
                                "-> str: %s\n",
                                rd, bufTime, bufStr);
        }

        close(fd);

        return 0;
}

输出结果

cpp 复制代码
readv OK, rd len: 39
-> time: Mon Dec  9 13:29:16 2024
-> str: hello writev!

readv OK, rd len: 39
-> time: Mon Dec  9 13:29:17 2024
-> str: hello writev!

readv OK, rd len: 39
-> time: Mon Dec  9 13:29:18 2024
-> str: hello writev!

readv OK, rd len: 39
-> time: Mon Dec  9 13:29:19 2024
-> str: hello writev!

readv OK, rd len: 39
-> time: Mon Dec  9 13:29:20 2024
-> str: hello writev!

readv OK, rd len: 39
-> time: Mon Dec  9 13:29:21 2024
-> str: hello writev!

readv OK, rd len: 39
-> time: Mon Dec  9 13:29:22 2024
-> str: hello writev!

5、truncate

有的时候我们需要清空文件,这时候可以使用truncate方法

cpp 复制代码
#include <unistd.h>
int truncate(const char *pathname, off_t length);
int ftruncate(int fd, off_t length)

以下是使用示例:

cpp 复制代码
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>
#include <fcntl.h>

#define MAX_SIZE 1024

int main(int argc, char **argv) {
        int ret = truncate("truncate.txt", 0);
        if(ret < 0) {
                printf("truncate fail! error msg: %s\n", strerror(errno));
                exit(1);
        } else {
                printf("truncate success!\n");
        }

        int fd1 = open("writev.txt", O_RDONLY);
        int fd2 = open("truncate.txt", O_RDWR);
        off_t offset = lseek(fd2, 0, SEEK_CUR);
        printf("truncate.txt offset: %ld\n", offset);

        char buf[MAX_SIZE];
        ssize_t rd = 0;
        int len = 0;
        while((rd = read(fd1, buf, MAX_SIZE)) > 0) {
                write(fd2, buf, rd);
                len += rd;
        }

        offset = lseek(fd2, 0, SEEK_CUR);
        printf("current truncate.txt offset: %ld\n", offset);

        offset = 0;
        while((rd = pread(fd2, buf, MAX_SIZE-1, offset)) > 0) {
                buf[rd] = '\0';
                printf("off:%ld -> %s\n", offset, buf);
                offset += rd;
        }

        ret = ftruncate(fd2, 0);

        if(ret < 0) {
                printf("ftruncate fail! error msg: %s\n", strerror(errno));
                exit(1);
        } else {
                printf("ftruncate success!\n");
        }

        offset = lseek(fd2, 0, SEEK_CUR);
        printf("after ftruncate, current truncate.txt offset: %ld\n", offset);

        close(fd1);
        close(fd2);

        return 0;
}

我们要注意的是,ftruncate执行完成之后,文件描述符的文件偏移量没有被修改!所以如果要重新写的话,得执行lseek。

6、mkstemp

有的时候我们想用临时文件,C库提供了一个mkstemp和 tmpfile给我们使用

cpp 复制代码
#include <stdlib.h>
int mkstemp(char *template);

template是文件名称,返回的是一个fd,但是删除的时候需要自己执行unlink方法。

cpp 复制代码
#include <stdlib.h>
#include <stdio.h>
#include <errno.h>
#include <string.h>
#include <unistd.h>

int main(int argc, char **argv) {

        char tmp[] = "./tmp.XXXXXX";
        int fd = mkstemp(tmp);
        if(fd < 0) {
                printf("can not create tmp file, err msg: %s\n", strerror(errno));
        } else {
                printf("create tmp file OK. fd: %d, filename: %s\n", fd, tmp);
        }
        unlink(tmp);

        return 0;
}

要注意的是,填入的文件名称最后6位必须是XXXXXX,否则将无法创建文件,会有Invalid argument。文件创建成功,tmp会自动变成新的文件名,所以不要用const!

cpp 复制代码
can not create tmp file, err msg: Invalid argument

如果我们自己不执行unlink,那么文件会一直存在

cpp 复制代码
-rw------- 1 xxx xxx     0 12月  9 14:52 tmp.gOq8Xb

tmpfile是stdio提供的一个方法,

cpp 复制代码
#include <stdio.h>
FILE *tmpfile();

它会创建一个文件流,程序结束时会自动删除该文件,不需要我们再手动删除。

相关推荐
jiuri_12155 分钟前
Linux UDP 编程详解
linux·udp
狄加山67526 分钟前
系统编程(进程通信--消息队列)
linux
电鱼智能的电小鱼1 小时前
基于SAIL-RK3576核心板的AI边缘计算网关设计方案——智慧家庭新突破
linux·人工智能·嵌入式硬件·边缘计算
CodeDevMaster2 小时前
解决 WSL 2 中 Ubuntu 22.04 安装 Docker 后无法启动的问题
linux·ubuntu·docker
老大白菜2 小时前
Ubuntu 手动安装 Open WebUI 完整指南
linux·运维·ubuntu
赤狐先生2 小时前
关于ubuntu命令行连接github失败解决办法
linux·ubuntu·github
hunter2062062 小时前
ubuntu常见指令详解
linux·ubuntu·postgresql
fanged2 小时前
LDD3学习9--数据类型和定时器
linux·学习
m0_748247552 小时前
如何安装linux版本的node.js
linux·运维·node.js
van叶~3 小时前
Linux探秘坊-------3.开发工具详解(1)
linux·运维·服务器