1.1 Linux 开发环境搭建
由于仅是开发环境的搭建,所以只简单记述一下步骤
必备软件:
-
Ubuntu 18.04
-
XShell-用于远程登录,使用SSH协议,TCP连接,端口号22
-
XFtp,本次实验中尚未用到
-
Visual studio code,安装扩展包:
-
Remote Development,用于建立远程连接管理
同样使用SSH建立连接
为了便于后期操作,windows端与ubuntu端使用相同的公钥
ssh-keygen -t rsa
此处也可以记录一下原本的过程:
(1)远程主机收到用户的登录请求,把自己的公钥发给用户。
(2)用户使用这个公钥,将登录密码加密后,发送回来。
(3)远程主机用自己的私钥,解密登录密码,如果密码正确,就同意用户登录。
-
中文软件包
-
大体本节实验就结束了
1.2 && 1.3 GCC
GCC(GNU Compiler Collection,GNU编译器套件)是由GNU开发的编程语言编译器。GNU还有其他语言,例如Java、Go的编译器套件,也包括了这些语言的库
GCC不仅支持C,还可以区分不同的C语言标准,支持用命令行选项来控制编译器在编译源代码时应该遵循哪个C标准。
查看标准可以使用如下命令
gcc/g++ -v/–version
编译文件的命令格式如下:
gcc 文件名.扩展名 -o 输出文件名
本节第二个内容有关于编程语言的发展:
第三个内容有关 GCC的工作流程:
预处理:指令-E
,举例如下:
gcc test.c -E -o test.i
编译:指令-S
gcc test.i -S -o test.s
汇编:指令-c
gcc test.s -s -o test.o
.o
文件已是可执行文件
第四个内容有关gcc和g++的区别
首先声明gcc和g++都是GNU(组织)的一个编译器
误区1:gcc只能用于编译c代码,g++只能编译c++代码。但实际上两者都可以
-
后缀为
.c
的gcc把它当做c程序,而g++当做是c++程序 -
后缀为
.cpp
的,两者都会认为是c++程序,c++的语法规则更加严谨一些 -
编译阶段,g++会调用gcc,对于c++代码,两者等价,但因为gcc命令不能自动和c++程序使用的库链接,所以通常用g++完成链接。
为了统一,索性直接用g++完成编译、链接,但实际上,gcc也可以完成
.cpp
程序的编译
误区2:gcc不会定义__cplusplus
宏,而g++会
- 实际上,该宏只是标志着编译器会把代码按照c还是c++语法来解释
- 如果后缀为
.c
,并且采用gcc编译器,那么宏就是未定义的;使用g++就是已定义
误区3:编译只能用gcc,链接只能用g++
-
严格来说,这句话不错,但是混淆了概念,应该说编译二者都可以用,而链接则可以用g++或者
gcc -lstdc++
-
gcc命令不能自动和C++程序使用的库链接,所以通常用g++来完成链接,但在编译阶段,二者等价
内容5,GCC常用参数选项
1.4 静态库的制作
此前也稍微了解过如何制作静态库
私密性好,方便部署与分发
简而言之需要先生成各文件的可执行文件,再将这些可执行文件封装入库
1.5 静态库的使用
1.6 动态库的制作与使用
1.7 动态库加载失败的原因
首先介绍一下动态库和静态库的工作原理:
-
静态库:
-
动态库:
-
程序启动之后,动态库会被动态加载到内存中,通过
ldd(list dynamic dependencies)
命令检查动态库依赖关系 -
如何定位共享库文件呢?
当系统加载可执行代码时,能够直到其所依赖的库的名字,但是还需要知道绝对路径。此时就需要系统的动态载入器来获取该绝对路径。
先后搜索
DT_RPATH
段→环境变量LD_LIBRARY_PATH
→etc/ld.so.cache
文件列表→/lib/,/usr/lib
目录(不推荐,因为文件众多)找到库文件后将其载入内存
1.8 解决动态库加载失败问题
1.9 静态库和动态库的对比
静态库制作过程:
动态库制作过程:
静态库的优缺点:
优点:
- 被打包到应用程序之中,加载速度快
- 发布程序无需提供静态库,移植方便
缺点:
- 消耗系统资源,浪费内存(不管用不用,都得占内存)
- 更新、部署、发布麻烦(每次修改都需要重新编译库文件)
动态库的优缺点:
优点:
- 可以实现进程间资源共享(共享库)
- 更新、部署、发布简单
- 可以控制何时加载动态库
缺点:
- 加载速度比静态库慢
- 发布程序时需要提供依赖的动态库
1.10/11/12 Makefile
什么是Makefile?
一个工程中源文件不计其数,按照类型、功能、模块分别放在若干个目录中。
Makefile文件定义了一系列的规则来指定那些文件需要先编译,哪些文件需要后编译,那些文件需要重新编译,甚至更复杂的功能操作。Makefile就像是一个shell脚本一样,也可以执行操作系统的命令。
文件一定要命名为makefile
或者Makefile
Makefile规则:
-
一个Makefile文件可以有一个或者多个规则:
目标 ...: 依赖 ... 命令(Shell命令) ...
- 目标:最终要生成的文件(伪目标除外)
- 依赖:生成目标所需要的文件或是目标
- 命令:通过执行命令对依赖操作生成目标(命令行前必须是Tab缩进)
-
Makefile中其他规则一般都是为了第一条规则服务的
Makefile工作原理:
- 命令在执行前,需要检查规则中的依赖是否存在
- 如果存在,执行命令
- 如果不存在,向下检查其它规则,检查有没有一个规则是用来生成这个依赖的,如果找到了,则执行该规则中的命令
- 检测更新,在执行规则中的命令时,会比较目标和依赖文件的时间
- 如果依赖的时间比目标的时间晚,需要重新生成目标
- 如果依赖的时间比目标的时间早,目标不需要更新,对应规则中的命令不需要被执行
规定定义的条数多一些,可以令模块更新更加自由,效率有可能会更高
Makefile中的变量:
-
自定义变量
变量名 = 变量值,例如
var=hello
-
预定义变量
AR:归档维护程序的名称,默认值为ar
CC:C编译器的名称,默认值为cc
CXX:C++编译器的名称,默认值为g++
$@:目标的完整名称
$<:第一个依赖文件的名称
$^:所有的依赖文件
-
获取变量的值
$(变量名)
Makefile中的模式匹配:
Makefile中的函数:
-
$(wildcard PATTERN ...)
-
功能:获取指定目录下指定类型的文件列表
-
参数:PATTERN 指的是某个或多个目录下的对应的某种类型的文件,如果有多个目录,一般使用空格间隔
-
返回:得到的若干个文件的文件列表,文件名之间使用空格间隔
-
示例:
$(wildcard *.c ./sub/*.c)
返回值格式:
a.c b.c c.c d.c e.c f.c
-
-
$(patsubst <pattern>,<repalcement>,<text>)
-
功能:查找
<text>
中的单词(单词以“空格”、“Tab”或“回车”“换行”分隔)是否符合模式<pattern>
,如果匹配的话,则以<replacement>
替换。 -
参数:
<pattern>
可以包括通配符%
,表示任意长度的字符串。如果<replacement>
中也包含%
,那么,<replacement>
中的这个%
将是<pattern>
中的那个%所代表的字符串。(可以用\
来转义,以\%
来表示%
字符串)。 -
返回:函数返回被替换过后的字符串
-
示例:
$(patsubst %.c, %.o, x.c bar.c)
返回值格式:
x.o bar.o
-
-
clean
属于目标中的一种,可以用于清除指定类型的文件
1.13~1.16GDB调试
GDB是GNU软件系统社区提供的调试工具,同GCC配套组成了一套完整的开发环境,GDB是Linux和许多类UNIX系统中的标准开发环境。
一般来说,GDB主要完成以下四个方面的功能:
- 启动程序,按照自定义的要求随心所欲的运行程序
- 可让被调试的程序在所指定的调试断点处停住(断点可以为条件表达式)
- 当程序被停住时,可以检查此时程序中所发生的事
- 可以改变程序,将一个BUG产生的影响修整从而测试其他BUG
GDB的官方吉祥物:射水鱼
准备工作:
- 通常,为调试而编译时,我们会关掉编译器的优化选项
-O
,并打开调试选项-g
。另外,-Wall
在尽量不影响程序行为的情况下选项打开所有的warning,也可以发现一些能避免bug的问题 gcc -g -Wall program.c -o program
-g
选项的作用是在可执行文件中加入源代码的信息,比如可执行文件中第几条机器指令对应源代码的第几行(但并不是把整个源文件嵌入到可执行文件中),所以在调试时必须保证gdb能找到源文件。
GDB中的常见命令
基础命令——
-
启动和退出
gdb 可执行程序
quit
或者q
-
设置参数(获取程序参数)
set args 10 20
show args
-
GDB 使用帮助
help
行号相关——
-
查看当前文件代码
list/l
从默认位置显示list/l 行号
从指定的行显示list/l 函数名
从指定的函数显示 -
查看非当前文件代码
list/l 文件名:行号
list/l 文件名:函数名
-
设置显示的行数
show list/listsize
set list/listsize 行数
断点相关——
-
设置断点
b/break 行号
b/break 函数名
b/break 文件名:行号
b/break 文件名:函数
-
查看断点
i/info b/break
-
删除断点
d/del/delete 断点编号
-
设置断点无效
dis/disable 断点编号
-
设置断点生效
ena/enable 断点编号
-
设置条件断点(一般用于循环的位置)
b/break 10 if i==5
正式调试相关——
-
运行GDB程序
start
程序停在第一行run
遇到断点才停 -
继续运行,到下一个断点停止
c/continue
-
向下执行一行代码(不会进入函数体)
n/next
-
变量操作
p/print 变量名
打印变量值ptype 变量名
打印变量类型 -
向下单步调试(遇到函数进入函数体)
s/step
finish
跳出函数体 -
自动变量操作
display num
自动打印指定变量的值i/info display
undisplay
编号 -
其他操作
set var 变量名=变量值
until
跳出循环
1.17 标准C库IO函数和Linux系统IO函数对比
标准c库IO函数——可以跨平台
主要区别在于调用与被调用:在Linux环境中,标准C库会调用Linux的系统函数
1.18 虚拟地址空间
当进程运行后,就相当于上图所示的一个系统模型
虚拟内存由CPU进行管理
用户区:
内核区:普通用户无权限操作
1.19 文件描述符
PCB属于内核区的内存管理模块,本质是一个复杂的结构体
每个进程都有一个文件描述符表,其是一个数组,大小为1024,前三位是默认被占用的
1.20 open打开文件
标准c库的api man 3 xxx
Linux的IO api man 2 xxx
1.21 open创建新文件
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
int open(const char *pathname, int flags, mode_t mode);
参数:
- pathname:
- flags:对文件的操作权限和其他的位置
- 必选项:O_RDONLY, O_WRONLY, or O_RDWR,三个选项相互互斥
- 可选项:O_CREAT 文件不存在,就创建新文件
- mode:八进制,表示创建出文件的操作权限,比如:0775
但最终的权限是:mode & ~umask(和用户权限有关)
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>
#include <unistd.h>
int main() {
// 打开一个文件
int fd = open("a.txt", O_RDONLY);
if(fd == -1) {
perror("open");
}
// 读写操作
// 关闭
close(fd);
return 0;
}
1.22 read、write函数
#include <unistd.h>
ssize_t read(int fd, void *buf, size_t count);
参数:
- fd:文件描述符,通过open函数返回
- buf:读入数据的存放地址,通常为数组地址
- count:指定缓冲区的大小
返回值:
- 成功:
>0:返回实际的读取到的字节数
=0:文件已经读取完了
- 失败:-1,并且设置errno(error number)
#include <unistd.h>
ssize_t write(int fd, const void *buf, size_t count);
参数:
- fd:同上
- buf:要往磁盘写入的数据
- count:要写的数据的实际大小
返回值:
- 成功:实际写入的字节数
- 失败:返回-1,设置errno
int main() {
int srcfd = open("english.txt", O_RDONLY);
if (srcfd == -1) {
perror("open");
return -1;
}
int destfd = open("cpy.txt", O_WRONLY | O_CREAT, 0664);
if (destfd == -1) {
perror("open");
return -1;
}
char buf[1024] = {0};
int len = 0;
while ((len = read(srcfd, buf, sizeof(buf))) > 0) {
write(destfd, buf, len);
}
close(destfd);
close(srcfd);
}
1.23 Iseek函数
标准C库函数
#include <stdio.h>
int fseek(FILE *stream, long offset, int whence);
Linux函数
#include <sys/types.h>
#include <unistd.h>
off_t lseek(int fd, off_t offset, int whence);
参数:
- fd:文件描述符,通过open得到的,通过这个fd操作某个文件
- offset:偏移量
- whence:
SEEK_SET
设置文件指针的偏移量
SEEK_CUR
设置偏移量:当前位置 + 第二个参数offset的值
SEEK_END
设置偏移量:文件大小 + 第二个参数offset的值
返回值:返回文件指针的位置
作用:
1.移动文件指针到文件头
lseek(fd, 0, SEEK_SET);
2.获取当前文件指针的位置
lseek(fd, 0, SEEK_CUR);
3.获取文件长度
lseek(fd, 0, SEEK_END);
4.拓展文件的长度,当前文件10b, 110b, 增加了100个字节
lseek(fd, 100, SEEK_END)
注意:需要写一次数据
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdio.h>
int main() {
int fd = open("hello.txt", O_RDWR);
if(fd == -1) {
perror("open");
return -1;
}
// 扩展文件的长度
int ret = lseek(fd, 100, SEEK_END);
if(ret == -1) {
perror("lseek");
return -1;
}
// 写入一个空数据
write(fd, " ", 1);
// 关闭文件
close(fd);
return 0;
}
1.24 stat、Istat函数
两个函数都有读取文件的信息有关
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
int stat(const char *pathname, struct stat *statbuf);
作用:获取一个文件相关的一些信息
参数:
- pathname:操作的文件的路径
- statbuf:结构体变量,传出参数,用于保存获取到的文件的信息
返回值:
成功:返回0
失败:返回-1 设置errno
int lstat(const char *pathname, struct stat *statbuf);
参数:
- pathname:操作的文件的路径
- statbuf:结构体变量,传出参数,用于保存获取到的文件的信息
返回值:
成功:返回0
失败:返回-1 设置errno
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <stdio.h>
int main() {
struct stat statbuf;
int ret = stat("a.txt", &statbuf);
if(ret == -1) {
perror("stat");
return -1;
}
printf("size: %ld\n", statbuf.st_size);
return 0;
}
1.25 模拟实现ls -l命令
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <pwd.h>
#include <grp.h>
#include <time.h>
#include <string.h>
// 模拟实现 ls -l 指令
// -rw-rw-r-- 1 nowcoder nowcoder 12 12月 3 15:48 a.txt
int main(int argc, char * argv[]) {
// 判断输入的参数是否正确
if(argc < 2) {
printf("%s filename\n", argv[0]);
return -1;
}
// 通过stat函数获取用户传入的文件的信息
struct stat st;
int ret = stat(argv[1], &st);
if(ret == -1) {
perror("stat");
return -1;
}
// 获取文件类型和文件权限
char perms[11] = {0}; // 用于保存文件类型和文件权限的字符串
switch(st.st_mode & __S_IFMT) {
case __S_IFLNK:
perms[0] = 'l';
break;
case __S_IFDIR:
perms[0] = 'd';
break;
case __S_IFREG:
perms[0] = '-';
break;
case __S_IFBLK:
perms[0] = 'b';
break;
case __S_IFCHR:
perms[0] = 'c';
break;
case __S_IFSOCK:
perms[0] = 's';
break;
case __S_IFIFO:
perms[0] = 'p';
break;
default:
perms[0] = '?';
break;
}
// 判断文件的访问权限
// 文件所有者
perms[1] = (st.st_mode & S_IRUSR) ? 'r' : '-';
perms[2] = (st.st_mode & S_IWUSR) ? 'w' : '-';
perms[3] = (st.st_mode & S_IXUSR) ? 'x' : '-';
// 文件所在组
perms[4] = (st.st_mode & S_IRGRP) ? 'r' : '-';
perms[5] = (st.st_mode & S_IWGRP) ? 'w' : '-';
perms[6] = (st.st_mode & S_IXGRP) ? 'x' : '-';
// 其他人
perms[7] = (st.st_mode & S_IROTH) ? 'r' : '-';
perms[8] = (st.st_mode & S_IWOTH) ? 'w' : '-';
perms[9] = (st.st_mode & S_IXOTH) ? 'x' : '-';
// 硬连接数
int linkNum = st.st_nlink;
// 文件所有者
char * fileUser = getpwuid(st.st_uid)->pw_name;
// 文件所在组
char * fileGrp = getgrgid(st.st_gid)->gr_name;
// 文件大小
long int fileSize = st.st_size;
// 获取修改的时间
char * time = ctime(&st.st_mtime);
char mtime[512] = {0};
strncpy(mtime, time, strlen(time) - 1);
char buf[1024];
sprintf(buf, "%s %d %s %s %ld %s %s", perms, linkNum, fileUser, fileGrp, fileSize, mtime, argv[1]);
printf("%s\n", buf);
return 0;
}
1.26 文件属性操作函数
/*
#include <unistd.h>
int access(const char *pathname, int mode);
作用:判断某个文件是否有某个权限,或者判断文件是否存在
参数:
- pathname: 判断的文件路径
- mode:
R_OK: 判断是否有读权限
W_OK: 判断是否有写权限
X_OK: 判断是否有执行权限
F_OK: 判断文件是否存在
返回值:成功返回0, 失败返回-1
*/
#include <unistd.h>
#include <stdio.h>
int main() {
int ret = access("a.txt", F_OK);
if(ret == -1) {
perror("access");
}
printf("文件存在!!!\n");
return 0;
}
/*
#include <sys/stat.h>
int chmod(const char *pathname, mode_t mode);
修改文件的权限
参数:
- pathname: 需要修改的文件的路径
- mode:需要修改的权限值,八进制的数
返回值:成功返回0,失败返回-1
*/
#include <sys/stat.h>
#include <stdio.h>
int main() {
int ret = chmod("a.txt", 0777);
if(ret == -1) {
perror("chmod");
return -1;
}
return 0;
}
/*
#include <unistd.h>
#include <sys/types.h>
int truncate(const char *path, off_t length);
作用:缩减或者扩展文件的尺寸至指定的大小
参数:
- path: 需要修改的文件的路径
- length: 需要最终文件变成的大小
返回值:
成功返回0, 失败返回-1
*/
#include <unistd.h>
#include <sys/types.h>
#include <stdio.h>
int main() {
int ret = truncate("b.txt", 5);
if(ret == -1) {
perror("truncate");
return -1;
}
return 0;
}
1.27 目录操作函数
/*
#include <sys/stat.h>
#include <sys/types.h>
int mkdir(const char *pathname, mode_t mode);
作用:创建一个目录
参数:
pathname: 创建的目录的路径
mode: 权限,八进制的数
返回值:
成功返回0, 失败返回-1
*/
#include <sys/stat.h>
#include <sys/types.h>
#include <stdio.h>
int main() {
int ret = mkdir("aaa", 0777);
if(ret == -1) {
perror("mkdir");
return -1;
}
return 0;
}
/*
#include <stdio.h>
int rename(const char *oldpath, const char *newpath);
*/
#include <stdio.h>
int main() {
int ret = rename("aaa", "bbb");
if(ret == -1) {
perror("rename");
return -1;
}
return 0;
}
/*
#include <unistd.h>
int chdir(const char *path);
作用:修改进程的工作目录
比如在/home/nowcoder 启动了一个可执行程序a.out, 进程的工作目录 /home/nowcoder
参数:
path : 需要修改的工作目录
#include <unistd.h>
char *getcwd(char *buf, size_t size);
作用:获取当前工作目录
参数:
- buf : 存储的路径,指向的是一个数组(传出参数)
- size: 数组的大小
返回值:
返回的指向的一块内存,这个数据就是第一个参数
*/
#include <unistd.h>
#include <stdio.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <fcntl.h>
int main() {
// 获取当前的工作目录
char buf[128];
getcwd(buf, sizeof(buf));
printf("当前的工作目录是:%s\n", buf);
// 修改工作目录
int ret = chdir("/home/nowcoder/Linux/lesson13");
if(ret == -1) {
perror("chdir");
return -1;
}
// 创建一个新的文件
int fd = open("chdir.txt", O_CREAT | O_RDWR, 0664);
if(fd == -1) {
perror("open");
return -1;
}
close(fd);
// 获取当前的工作目录
char buf1[128];
getcwd(buf1, sizeof(buf1));
printf("当前的工作目录是:%s\n", buf1);
return 0;
}
1.28 目录遍历函数
/*
// 打开一个目录
#include <sys/types.h>
#include <dirent.h>
DIR *opendir(const char *name);
参数:
- name: 需要打开的目录的名称
返回值:
DIR * 类型,理解为目录流
错误返回NULL
// 读取目录中的数据
#include <dirent.h>
struct dirent *readdir(DIR *dirp);
- 参数:dirp是opendir返回的结果
- 返回值:
struct dirent,代表读取到的文件的信息
读取到了末尾或者失败了,返回NULL
// 关闭目录
#include <sys/types.h>
#include <dirent.h>
int closedir(DIR *dirp);
*/
#include <sys/types.h>
#include <dirent.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
int getFileNum(const char * path);
// 读取某个目录下所有的普通文件的个数
int main(int argc, char * argv[]) {
if(argc < 2) {
printf("%s path\n", argv[0]);
return -1;
}
int num = getFileNum(argv[1]);
printf("普通文件的个数为:%d\n", num);
return 0;
}
// 用于获取目录下所有普通文件的个数
int getFileNum(const char * path) {
// 1.打开目录
DIR * dir = opendir(path);
if(dir == NULL) {
perror("opendir");
exit(0);
}
struct dirent *ptr;
// 记录普通文件的个数
int total = 0;
while((ptr = readdir(dir)) != NULL) {
// 获取名称
char * dname = ptr->d_name;
// 忽略掉. 和..
if(strcmp(dname, ".") == 0 || strcmp(dname, "..") == 0) {
continue;
}
// 判断是否是普通文件还是目录
if(ptr->d_type == DT_DIR) {
// 目录,需要继续读取这个目录
char newpath[256];
sprintf(newpath, "%s/%s", path, dname);
total += getFileNum(newpath);
}
if(ptr->d_type == DT_REG) {
// 普通文件
total++;
}
}
// 关闭目录
closedir(dir);
return total;
}
1.29 dup、dup2函数
/*
#include <unistd.h>
int dup(int oldfd);
作用:复制一个新的文件描述符
fd=3, int fd1 = dup(fd),
fd指向的是a.txt, fd1也是指向a.txt
从空闲的文件描述符表中找一个最小的,作为新的拷贝的文件描述符
*/
#include <unistd.h>
#include <stdio.h>
#include <fcntl.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <string.h>
int main() {
int fd = open("a.txt", O_RDWR | O_CREAT, 0664);
int fd1 = dup(fd);
if(fd1 == -1) {
perror("dup");
return -1;
}
printf("fd : %d , fd1 : %d\n", fd, fd1);
close(fd);
char * str = "hello,world";
int ret = write(fd1, str, strlen(str));
if(ret == -1) {
perror("write");
return -1;
}
close(fd1);
return 0;
}
/*
#include <unistd.h>
int dup2(int oldfd, int newfd);
作用:重定向文件描述符
oldfd 指向 a.txt, newfd 指向 b.txt
调用函数成功后:newfd 和 b.txt 做close, newfd 指向了 a.txt
oldfd 必须是一个有效的文件描述符
oldfd和newfd值相同,相当于什么都没有做
*/
#include <unistd.h>
#include <stdio.h>
#include <string.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <fcntl.h>
int main() {
int fd = open("1.txt", O_RDWR | O_CREAT, 0664);
if(fd == -1) {
perror("open");
return -1;
}
int fd1 = open("2.txt", O_RDWR | O_CREAT, 0664);
if(fd1 == -1) {
perror("open");
return -1;
}
printf("fd : %d, fd1 : %d\n", fd, fd1);
int fd2 = dup2(fd, fd1);
if(fd2 == -1) {
perror("dup2");
return -1;
}
// 通过fd1去写数据,实际操作的是1.txt,而不是2.txt
char * str = "hello, dup2";
int len = write(fd1, str, strlen(str));
if(len == -1) {
perror("write");
return -1;
}
printf("fd : %d, fd1 : %d, fd2 : %d\n", fd, fd1, fd2);
close(fd);
close(fd1);
return 0;
}
1.30 fcntl函数
/*
#include <unistd.h>
#include <fcntl.h>
int fcntl(int fd, int cmd, ...);
参数:
fd : 表示需要操作的文件描述符
cmd: 表示对文件描述符进行如何操作
- F_DUPFD : 复制文件描述符,复制的是第一个参数fd,得到一个新的文件描述符(返回值)
int ret = fcntl(fd, F_DUPFD);
- F_GETFL : 获取指定的文件描述符文件状态flag
获取的flag和我们通过open函数传递的flag是一个东西。
- F_SETFL : 设置文件描述符文件状态flag
必选项:O_RDONLY, O_WRONLY, O_RDWR 不可以被修改
可选性:O_APPEND, O)NONBLOCK
O_APPEND 表示追加数据
NONBLOK 设置成非阻塞
阻塞和非阻塞:描述的是函数调用的行为。
*/
#include <unistd.h>
#include <fcntl.h>
#include <stdio.h>
#include <string.h>
int main() {
// 1.复制文件描述符
// int fd = open("1.txt", O_RDONLY);
// int ret = fcntl(fd, F_DUPFD);
// 2.修改或者获取文件状态flag
int fd = open("1.txt", O_RDWR);
if(fd == -1) {
perror("open");
return -1;
}
// 获取文件描述符状态flag
int flag = fcntl(fd, F_GETFL);
if(flag == -1) {
perror("fcntl");
return -1;
}
flag |= O_APPEND; // flag = flag | O_APPEND
// 修改文件描述符状态的flag,给flag加入O_APPEND这个标记
int ret = fcntl(fd, F_SETFL, flag);
if(ret == -1) {
perror("fcntl");
return -1;
}
char * str = "nihao";
write(fd, str, strlen(str));
close(fd);
return 0;
}