Linux 系统编程入门
gcc 编译命令
1 2 3 4 5 6 7 8 9 10 11
| gcc [FileName] [-o ProgramName]
# 常用编译选项 [-o] 目标文件名 [-E] 预处理,生成预处理后的代码,不编译 [-S] 编译,生成汇编代码,不汇编 [-c] 汇编,生成目标代码,但是不进行链接 [-I directory] 指定 include 包含文件的搜索目录 [-g] 在编译的时候,生成调试信息,该程序可以被调试器调试 [-D] 编译的时候定义一个宏 [-l] 链接的库
|
g++ 在编译的阶段调用 gcc,但是 gcc 不能自动和 c++ 程序使用的库链接,除非添加编译选项 gcc -lstdc++
。
静态库和动态库
静态库的制作
命名规则:
- Linux:libxxx.a
- Windows:libxxx.lib
静态库的制作
gcc 编译生成 -o 汇编文件
gcc -c a.c b.c
对汇编文件打包,创建静态库
ar rcs libxxx.a a.o b.o
静态库的使用
xxx
为 库名称,而非库文件名称
动态库的制作
命名规则:
- Linux:libxxx.so
- Windows:libxxx.dll
动态库的制作
gcc 编译生成 -o 汇编文件
gcc -c -fpic/-fPIC a.c b.c
gcc 制作动态库
gcc -shared *.o -o libxxx.so
动态库的使用
程序运行时动态库加载失败的原因:
系统加载可执行代码的时候,除了需要知道所依赖的动态库的名称,还需要知到其绝对路径,此时需要系统的动态载入器来获取其绝对路径。
解决方法:
添加环境变量,只在当前终端生效(临时)
export LD_LIBRARY_PATH = $LD_LIBRARY_PATH:libso_path
添加环境变量,在~/.bashrc
中添加(用户级别)
export LD_LIBRARY_PATH = $LD_LIBRARY_PATH:libso_path
使环境变量生效
source ~/.bashrc
或者 ,~/.bashrc
添加环境变量,在/etc/profile
中添加(系统级别)
export LD_LIBRARY_PATH = $LD_LIBRARY_PATH:libso_path
使环境变量生效
source /etc/profile
或者 ,/etc/profile
修改 /etc/ld.so.cache
,二进制文件,不能直接修改,通过 /etc/ld.so.conf
修改,直接添加libso_path
,ldconfig
使修改生效。
将动态库文件放在 /lib/
或者 /usr/lib/
目录下(不建议),可能会替换同名的系统库文件。
静态库 vs. 动态库
静态库
优点:
a. 加载速度快
b. 发布程序无需提供静态库,移植方便
缺点:
a. 消耗系统资源,浪费内存
b. 更新、部署、发布麻烦
动态库
优点:
a. 可以实现进程间的资源共享
b. 更新、部署、发布简单
c. 可以控制核实加载动态库
缺点:
a. 加载速度比静态库慢
b. 发布程序是需要提供依赖的动态库
Makefile
什么是 Makefile
- 工程的源文件根据类型、功能、模块分别放在不同的目录中,Makefile 文件定义了以系列规则来指定哪些文件先编译,哪些文件后编译,哪些文件需要重新编译,甚至更复杂的功能。
- Makefile 带来的好处:“自动化编译”,make 是解释 Makefile 的命令工具。
Makefile 文件命名和规则
文件命名:
Makefile 或者 makefile
规则:
1 2 3 4 5 6 7 8 9
| 目标 ... : 依赖... 命令 (shell 命令) ...
目标: 最终要生成的文件 (伪目标除外) 依赖: 生成目标所需要的文件或是目标 命令: 通过执行命令对依赖操作生成目标 (前有缩进)
Makefile 文件里的其他规则一般是为第一条规则服务的.
|
例子
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70
| app:sub.c add.c mult.c div.c main.c gcc sub.c add.c mult.c div.c main.c -o app
app:sub.o add.o mult.o div.o main.o gcc sub.o add.o mult.o div.o main.o -o app
sub.o:sub.c gcc -c sub.c -o sub.o
add.o:add.c gcc -c add.c -o add.o
mult.o:mult.c gcc -c mult.c -o mult.o
div.o:div.c gcc -c div.c -o div.o
main.o:main.c gcc -c main.c -o main.o
src=sub.o add.o mult.o div.o main.o target=app $(target):$(src) $(CC) $(src) -o $(target)
sub.o:sub.c gcc -c sub.c -o sub.o
add.o:add.c gcc -c add.c -o add.o
mult.o:mult.c gcc -c mult.c -o mult.o
div.o:div.c gcc -c div.c -o div.o
main.o:main.c gcc -c main.c -o main.o
src=sub.o add.o mult.o div.o main.o target=app $(target):$(src) $(CC) $(src) -o $(target)
%.o:%.c $(CC) -c $< -o $@
src=$(wildcard ./*.c) objs=$(patsubst %.c, %.o, $(src)) target=app $(target):$(objs) $(CC) $(objs) -o $(target)
%.o:%.c $(CC) -c $< -o $@
.PHONY:clean clean: rm $(objs) -f
|
GDB 调试
命令:
向可执行程序中加入调试信息.
启动/退出 gdb:
1 2 3
| gdb [可执行文件] # 启动
(gdb) q/quit # 退出 gdb
|
gdb 常用命令:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62
| (gdb) set args 10 20 (gdb) show args
(gdb) help [command]
(gdb) list/l (gdb) list/l line (gdb) list/l func
(gdb) list/l filename : line (gdb) list/l filename : func
(gdb) show list/listsize (gdb) set list/listsize line
(gdb) b/break line (gdb) b/break func (gdb) b/break filename : line (gdb) b/break filename : func
(gdb) i/info b/break
(gdb) d/del/delete brk_num
(gdb) dis/disable brk_num
(gdb) en/enable brk_num
(gdb) b/break 10 if i==5
(gdb) start (gdb) run
(gdb) c/continue (gdb) n/next (gdb) s/step (gdb) finish
(gdb) p/print variable (gdb) ptype variable
(gdb) display variable (gdb) i/info display (gdb) undisplay no.
(gdb) set var variable = value (gdb) until
|
gdb 不输入命名按回车默认执行上一条命令。
文件 IO
标准 C 库 IO 函数
标准 C 库中的IO函数 调用操作系统的 API,可以跨平台。
标准 C 库的IO函数带有缓冲区,效率较高。
虚拟地址空间
32位操作系统的虚拟地址空间:
文件描述符
1 2 3 4
| # 文件描述符 0 -> STDIN_FILENO # 标准输入 1 -> STDOUT_FILENO # 标准输出 2 -> STDERR_FILENO # 标准错误
|
文件描述符表是一个数组,默认大小为1024。前三个元素被占用,默认打开,指向当前终端。
Linux 系统 IO 函数
1 2 3 4 5 6 7 8
| int open(const char *pathname, int flags); int open(const char *pathname, int flags, mode_t mode); int close(int fd); ssize_t read(int fd, void *buf, size_t count); ssize_t write(int fd, const void *buf, size_t count); off_t lseek(int fd, off_t offset, int whence); int stat(const char *pathname, struct stat *statbuf); int lstat(const char *pathname, struct stat *statbuf);
|
可使用man
命令查看说明文档:
open 系统调用
1 2 3 4 5 6 7
| # include <sys/types.h> # include <sys/stat.h> # include <fcntl.h>
int open(const char *pathname, int flags); int open(const char *pathname, int flags, mode_t mode);
|
C 没有函数重载, 两个 open 通过可变参数实现:
1
| int open(const char *pathname, int flags, ...);
|
打开一个已存在文件:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39
|
# 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 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39
|
# include <sys/types.h> # include <sys/stat.h> # include <fcntl.h> # include <stdio.h> # include <unistd.h>
int main() {
int fd = open("create.txt", O_RDWR | O_CREAT, 0777);
if(fd == -1) { perror("open"); }
close(fd);
return 0; }
|
read、write
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60
|
#include <unistd.h> #include <stdio.h> #include <sys/types.h> #include <sys/stat.h> #include <fcntl.h>
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);
return 0; }
|
lseek
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67
|
#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, 10000000, SEEK_END); if(ret == -1) { perror("lseek"); return -1; }
write(fd, ".", SEEK_CUR);
close(fd);
return 0; }
|
什么是空洞文件(hole file)?在Linux中,lseek的系统调用是可以改变在文件上面的偏移量的,而且还允许其超出文件的长度。偏移量一旦超出了文件的长度,下一次进行文件IO写入操作文件的时候便会延续偏移量的位置继续写入,进而在文件中间产生了空洞的部分,这部分会以”\0”填充,而从原来的文件结尾到新写入数据间的这段空间就被称为“文件空洞”。
在Linux中,EOF(文件结束符)并不是一个字符,而是在读取到文件末尾的时候返回的一个信号值,也就是-1。
文件空洞部分实际上是不会占用任何的物理空间的,直到在某个时刻对空洞部分进行写入文件内容的时候才会为它分配对应的空间。但是在空洞文件形成的时候,逻辑上面的文件大小是分配了空洞部分的大小的。
生成元素文件 hello.txt 的拷贝:
编译上述代码后运行。
使用od命令来查看hello.txt
文件的二进制内容
使用ls查看文件的逻辑大小:
逻辑大小:
hello.prev: 11 Bytes
hello.txt: 10000012 Bytes = 10000012 / 1024 / 1024 M = 9.6 M
使用du查看文件实际占用物理块的大小:
文件系统的基本操作单位是数据块,Linux 中的逻辑块大小为 4KB
,所以 hello.prev 实际占用的空间为 4k,由于 "hello,world"
和 "."
在逻辑上不在一个块内,所以 hello.txt 实际占用的空间为 8k,远小于 9.6 M,所以文件空洞部分实际上是不会占用任何的物理空间的。
stat、lstat
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
|
|
stat
和lstat
的区别:当文件是一个符号链接时,lstat
返回的是该符号链接本身的信息;而stat
返回的是该链接指向的文件的信息。
stat 结构体:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| struct stat { dev_t st_dev; ino_t st_ino; mode_t st_mode; nlink_t st_nlink; uid_t st_uid; gid_t st_gid; dev_t st_rdev; off_t st_size; blksize_t st_blksize; blkcnt_t st_blocks; time_t st_atime; time_t st_mtime; time_t st_ctime; };
|
使用stat命令查看文件的stat结构体(没有lstat命令):
hello.txt : Blocks = 8K / 512B = 16
hello.prev: Blocks = 4K / 512B = 8
st_mode 变量(16位):
文件属性的操作函数
1 2 3 4
| int access(const char *pathname, int mode); int chmod(const char *filename, int mode); int chown(const char *path, uid_t owner, gid_t group); int truncate(const char *path, off_t length);
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36
|
|
lseek 和 truncate 的区别
lseek 修改的是文件偏移量(读写位置),truncate修改的是文件的文件长度(逻辑大小),如果 lseek 文件偏移量大于文件之后不在该位置进行写入,则无法扩展文件长度(逻辑大小)。
lseek 和 truncate 产生的文件空洞都不会占用实际物理磁盘空间。
lseek 不进行写入:
truncate:
目录的操作函数
1 2 3 4 5
| int rename(const char *oldpath, const char *newpath); int chdir(const char *path); char *getcwd(char *buf, size_t size); int mkdir(const char *pathname, mode_t mode); int rmdir(const char *pathname);
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30
|
|
目录的遍历函数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57
| DIR *opendir(const char *name); struct dirent *readdir(DIR *dirp); int closedir(DIR *dirp);
struct dirent { ino_t d_ino; off_t d_off; unsigned short int d_reclen; unsigned char d_type; char d_name[256]; };
|
读取某个目录下所有的普通文件的个数:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72
| #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) {
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; }
|
dup、dup2
1 2
| int dup(int oldfd); int dup2(int oldfd, int newfd);
|
dup 函数示例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44
|
#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; }
|
dup2 函数示例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54
|
#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; }
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; }
|
fcntl 函数
1 2
| int fcntl(int fd, int cmd, ... );
|
示例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64
|
#include <unistd.h> #include <fcntl.h> #include <stdio.h> #include <string.h>
int main() {
int fd = open("1.txt", O_RDWR); if(fd == -1) { perror("open"); return -1; }
int flag = fcntl(fd, F_GETFL); if(flag == -1) { perror("fcntl"); return -1; } flag |= O_APPEND;
int ret = fcntl(fd, F_SETFL, flag); if(ret == -1) { perror("fcntl"); return -1; }
char * str = "hello"; write(fd, str, strlen(str));
close(fd);
return 0; }
|