第一章 Linux系统编程入门
1.1Linux开发环境搭建
环境:Ubuntu20.04
编译软件:VScode
编译:GCC
调试:GDB
1.2-1.3 GCC
- 工作流程
- 预处理
-
gcc -E test.c -o test.i
-
- 编译
-
gcc -S test.i -o test.s
-
- 汇编
-
gcc -c test.s -o test.o
-
- 链接
-
可直接执行 ./xxx.o
-
- gcc和g++的区别
- 1、gcc、g++可以相互编译
- 2、编译可以用gcc/g++,链接可以用g++/gcc -lstdc++
- gcc常用参数选项
1.4-1.5 静态库
- 定义:在程序的链接阶段被复制到了程序中
- 优缺点
- 优:加载速度快、移植方便
- 缺:消耗系统资源、浪费内存、更新、部署麻烦
- 命名规则
- libxxx.a
- 制作
- 1、gcc获得.o文件
- 2、将.o 文件打包,使用ar工具
ar rcs libxxx.a xxx.o xxx.o
1.6 动态库
- 定义:在链接阶段没有被复制到程序中,而是在程序运行时由系统动态加载到内存中供程序调用
- 优缺点
- 优:实现进程间资源共享
- 缺:加载速度慢、依赖提供的动态库
- 命名规则
- libxxx.so
- 制作
- 1、gcc得到.o文件,得到与位置无关的代码
-
gcc -c -fpic/-fPIC a.c b.c
-
- 2、得到动态库
-
gcc -shared a.o b.o -o libcalc.so
-
- 1、gcc得到.o文件,得到与位置无关的代码
1.7两者的工作原理
- 静态库:gcc链接时,把静态库中的代码打包到可执行程序中
- 动态库: gcc链接时,不会把动态库中的代码打包到可执行程序中
- 定位共享库文件
- 搜索DT_RPATH段
- 环境变量LD_LIBRARY_PATH
- /etc/ld.so.cache文件列表
- /lib/,/usr/lib目录
1.10 Makefile
- 功能:定义了一系列指定哪些文件需要先编译、哪些文件需要后编译、哪些需要重新编译。
- 好处:自动化编译
- 命名规则
- 文件名
- makefile/Makefile
- 规则
- 其他规则一般都是为第一条规则服务的
- 文件名
-
变量
-
函数
-
wildcard(查找函数)
注释:寻找当前目录和./sub/目录下为.c的文件
-
patsubst(替换函数)
注释:把.c的文件替换为.o文件
-
1.13GDB调试
- 功能
- 断点调试
- 准备工作
-
gcc -g -Wall xxxx.c -o xxxx
-
注:-g是在可执行文件中加入源代码的信息,并不是把整个源文件嵌入到可执行文件中
- GDB的常用命令
以下部分开始讨论UNIX系统,
1.19文件描述符
- 对内核而言,所有打开的文件都通过文件描述符引用
- 分类
- 0:进程的标准输入
- 1:标准输出
- 2:标准错误
1.20 open函数
- 定义
- 返回值
- 若成功,返回文件描述符fd
- 失败,返回-1
- 使用事项
- 三个头文件
- 参数1:要打开的文件名
- 参数2:函数的多个选项(进行“或”运算构成oflag参数)
- 必选:O_RDONLY(只读)、O_WRONLY(只写)、O_RDWR(读写)、
- 可选:O_APPEND(追加到文件的尾端)、O_CREAT(修改文件的访问权限)....
- 举例
#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、wirte函数
- read
- 定义
- 返回值
- 读到的字节数,若已到文件尾,返回0
- 出错,返回-1
- 参数1:源文件的文件描述符
- 参数2:读取数据存放的地方,一般为一个数组
- 参数3:数组的大小,使用sizeof函数
- 注意头文件#include<stdio.h>
- 定义
- wirte
- 定义
- 返回值
- 若成功,返回已写的字节数
- 若出错,返回-1
- 参数1:目标文件的文件描述符
- 参数2:读取数据存放的地方
- 参数3:要写的数据实际大小
- 定义
- 举例
#include<sys/types.h> #include<sys/stat.h> #include<fcntl.h> #include<stdio.h> #include<unistd.h> int main() { //1.通过open打开english.txt文件 int srcfd = open("english.txt",O_RDONLY); if(srcfd == -1) { perror("open"); return -1; } //2.创建一个新的文件 int destfd = open("copy.txt",O_WRONLY | O_CREAT,0664); if(destfd == -1) { perror("open"); return -1; } //3.频繁的读写操作 char buf[1024]={0}; int len =0; while((len = read(srcfd,buf,sizeof(buf))) >0 ) { write(destfd, buf, len); } //4.关闭文件 close(srcfd); close(destfd); }
1.23 lseek函数
- 定义:对文件指针进行操作
- 返回值
- 若成功,返回新的文件偏移量
- 出错,返回-1
- 参数2offset与参数3 whence有关
- 若whence为 SEEK_SET, 将文件的偏移量设置为距文件开始处offset个字节
- 若whence为 SEEK_CUR, 将文件的偏移量设置为其当前值加offset字节,可正可负
- 若whence为 SEEK_END, 将文件的偏移量设置为文件长度加offset字节,可正可负
- 作用
- 1、移动文件指针到文件头
- 2、获取当前文件指针的位置
- 3、获取文件的长度
- 4、拓展文件的长度
- 举例
#include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> #include <stdio.h> #include <unistd.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); }
-
1.24stat函数
-
定义:获取具体文件的信息
-
返回值
-
若成功,返回0;
-
出错,返回-1
-
-
参数1:要操作的文件名路径
-
参数2:结构体变量,包括文件的相关信息
-
mode_t:文件的类型和存取的权限
-
st_size:文件字节数
-
等
-
-
举例(获取该文件的大小)
#include <sys/types.h> #include <sys/stat.h> #include <fcntl.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使用stat函数获取文件的信息(实现ls -l的功能)
- 代码
//模拟实现ls -l的指令 #include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> #include <stdio.h> #include <pwd.h> #include <grp.h> #include <time.h> #include <string.h> 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_IFSOCK: perms[0]='s'; break; case S_IFLNK: perms[0]='l'; break; case S_IFREG: perms[0]='-'; break; case S_IFBLK: perms[0]='b'; break; case S_IFDIR: perms[0]='d'; break; case S_IFCHR: perms[0]='c'; 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 linuNum = 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,linuNum,fileUser,fileGrp ,fileSize,mtime,argv[1]); printf("%s\n",buf); return 0; }
1.26文件属性操作函数
- access:判断文件是否存在,是否具有某些权限
- 定义
- 返回值
- 若成功,返回0
- 出错,返回-1
- 参数1:所要操作的文件名
- 参数2:测试信息mode
- R_OK:测试读权限
- W_OK:测试写权限
- X_OK:测试执行权限
- 举例
#include <unistd.h> #include <stdio.h> int main() { int ret = access("a.txt",F_OK); if(ret == -1 ) { perror("access"); } printf("文件存在!!\n"); return 0; }
- 定义
- chmod:修改文件信息
- 定义
- 返回值
- 若成功,返回0;
- 出错,返回-1
- 参数1:所要修改的文件名
- 参数2:常量的按位或,如0775
- 举例
#include <sys/stat.h> #include <stdio.h> int main() { int ret = chmod("a.txt",0775); if(ret == -1 ) { perror("chmod"); return -1; } return 0; }
- 定义
- chown:修改文件的用户ID和组ID
- truncate:缩减或扩展文件的尺寸至指定大小
- 定义
- 返回值
- 若成功,返回0;
- 出错,返回-1
- 参数1:要修改的文件名
- 参数2:所要调整的文件大小
- 举例
#include <unistd.h> #include <sys/types.h> #include <stdio.h> int main() { int ret = truncate("a.txt",20); if(ret == -1) { perror("truncate"); } return 0; }
- 定义
1.27 目录操作函数
- rename:更改目录名
- 定义
- 返回值
- 若成功,返回0;
- 出错,返回-1
- 参数1:原来的目录名
- 参数2:新的目录名
- 举例
#include<stdio.h> int main() { int ret = rename("aaa","bbb"); if(ret == -1) { perror("rename"); return -1; } return 0; }
- 定义
- chdir:修改工作目录
- 定义
- 返回值
- 若成功,返回0;
- 出错,返回-1
- 参数1:要修改的工作目录
- 定义
- getcwd:获取当前的工作路径
- 定义
-
-
- 返回值
- 若成功,返回buf
- 出错,返回NULL
- 参数1:缓冲区地址
- 参数2:缓冲区的长度
- 举例
#include <unistd.h> #include <stdio.h> #include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> int main() { //1、获取当前的工作目录 char buf[128]; getcwd(buf,sizeof(buf)); printf("当前的工作目录为:%s\n",buf); //2、修改工作目录 int ret = chdir("/home/metrorise/linux/lesson1-26"); if(ret == -1) { perror("chdir"); return -1; } //3、创建新的文件 int fd = open("chdir.txt",O_CREAT | O_RDWR,0664); if(fd == -1) { perror("open"); return -1; } //4、修改后的工作目录 char buf1[128]; getcwd(buf1,sizeof(buf)); printf("当前的工作目录为:%s\n",buf1); //5、关闭 close(fd); }
- 定义
- mkdir:创建目录
- rmdir:删除目录
1.28 目录遍历函数
- opendir:打开一个目录
- 定义
- 返回值
- 若成功,返回DIR *类型,目录流
- 出错,返回NULL
- 参数1:需要打开的目录名称
- 定义
- readdir:读取目录中的数据
- 定义
-
-
- 返回值
- 若成功,返回读取到的文件信息
- 出错,返回NULL
- 参数:dirp是opendir返回的结果
- 定义
- closedir:关闭目录
- 举例
#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]); //输入格式:./xx 目标目录 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; // 忽略掉. 和.. strcmp比较字符串 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函数
- dup:复制一个现有的文件描述符
- dup2:重定向文件描述符
- 定义
-
-
- 返回值
- 若成功,返回新的文件描述符
- 出错,返回-1
- 参数1:旧的文件描述符
- 参数2:新的文件描述符
- 举例
#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函数
- 主要功能
- 1、复制文件描述符
- 2、设置/获取文件的状态标志
- 定义
-
-
- 返回值
- 若成功,依赖于cmd
- 出错,返回-1
- 参数2:命令
- F_DUPFD : 复制文件描述符,复制的是第一个参数fd,得到一个新的文件描述符(返回值)
- F_GETFL : 获取指定的文件描述符文件状态flag
- F_SETFL : 设置文件描述符文件状态flag
- 代码
#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; }