第1章Linux系统编程入门

本文详细介绍了VMware的三种网络模式——桥接、NAT和仅主机模式,以及GCC的编译流程,包括预处理、编译、汇编和链接。此外,还讨论了静态库与动态库的制作和使用,以及Makefile的规则和工作原理。同时,涵盖了GDB调试技巧、文件描述符、系统IO函数和Linux内存管理,如虚拟地址空间的分配。最后,文章讲解了如何实现文件的读写、权限管理以及目录操作等基础系统编程知识。
摘要由CSDN通过智能技术生成

VMware虚拟机的三种网络模式

桥接、NAT、仅主机的区别

GCC工作流程

	源代码(.h.c.cpp)
		|	预处理
		|	(头文件展开、删除注释、宏替换等)
		↓	gcc test.c -E -o test.i
	预处理后源代码(.i)
		|	编译器
		|
		↓ 	gcc test.i -S -o test.s
	汇编代码(.s)
		|	汇编器
		|
		↓	gcc test.s -c -o test.o
	目标代码(.o)	+启动代码、库代码、其他目标代码
					|	链接器
					|
					↓
				可执行程序(.exe.out)

GCC和G++

.c的,gcc当作c程序,g++当作c++程序
.cpp的,都当作c++程序
编译阶段,g++调用gcc,对于c++代码二者等价
但链接时,c++程序需要用g++或者gcc -lstdc++

代码保密
方便部署和分发
	静态库优点:
					被打包到应用程序中,加载速度快
					发布程序无需提供静态库,移植方便
			   缺点:
			   		消耗系统资源,浪费内存
			   		更新、部署、发布麻烦
	动态库优点:
					可以实现进程间资源共享(共享库)
					更新、部署、发布简单
					可以控制何时加载动态库
			   缺点:
			   		加载速度比静态库慢
			   		发布程序时需要提供依赖的动态库

静态库:
  GCC链接阶段被复制到可执行文件中。
动态库:
  GCC链接阶段没有被复制到可执行文件中,只放了信息,而在程序运行时,使用到动态库的api时,根据定位动态库的绝对路径,由系统动态加载到内存中。可通过ldd(list dynamic dependencies)命令检查动态库依赖关系(ldd 可执行文件)

定位动态库文件:
  当系统加载可执行代码时,能够知道其所依赖的库的名字,但还需要知道绝对路径。
  此时就需要系统的动态载入器来获取该绝对路径。
  对于elf格式(绝大部分)的可执行程序,由 ld-linux.so动态载入器(专门用来负责定位、加载程序所需的所有动态库文件)来完成。
  它先后搜索elf文件的 DT_RPATH段 (改不了,不考虑) --→ 环境变量LD_LIBRARY_PATH --→ /etc/ld.so.cache文件列表 --→ /lib 或 /user/lib目录(内含系统自带库文件,不建议)找到库文件后将其载入到内存

静态库的制作和使用

  1. 命名规则
    Linux:libxxx.a
    lib:前缀
    xxx:库名
    .a:后缀
    Windows:libxxx.lib

  2. 制作
    gcc -c 获得 .o文件
    将 .o文件打包,使用ar工具(archive)
     ar rcs libxxx.a xxx.o xxx.o
        r - 将文件插入备存文件中
        c -建立备存文件
        s -索引

  3. 使用
    分发库文件时需要将库文件的头文件同时分发

    使用-I(大i)去查找头文件所在的路径
    gcc main.c -o app -I ./include
    然后使用-l(小L)指定使用的库的名称
    gcc main.c -o app -I ./include -l calc
    再使用-L去查找库文件所在的路径
    gcc main.c -o app -I ./include -l calc -L ./lib

动态库的制作和使用

  1. 命名规则
    Linux:libxxx.so
    lib:前缀
    xxx:库名
    .so:后缀
    Windows:libxxx.dll

  2. 制作
    使用gcc得到.o文件,得到和位置无关的文件
    gcc -c -fPIC a.c b.c
    gcc -c -fpic a.c b.c
    使用gcc得到动态库
    gcc -shared a.o b.o -o libcalc.so

  3. 使用
    分发库文件时需要将库文件的头文件同时分发

    使用-I(大i)去查找头文件所在的路径
    gcc main.c -o app -I ./include
    然后使用-l(小L)指定使用的库的名称
    gcc main.c -o app -I ./include -l calc
    再使用-L去查找库文件所在的路径
    gcc main.c -o app -I ./include -l calc -L ./lib

    配置动态库绝对路径:
    1.1.临时配置环境变量
    export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:动态库绝对路径
    1.2.永久用户级别配置
     进入到home目录下
    vim .bashrc
     到最后一行
     插入export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:动态库绝对路径
     保存并退出
    . .bashrc第一个.是source的意思
     或
    source .bashrc
     使之生效
    1.3.永久系统级别配置
    sudo vim /etc/profile
     到最后一行
     插入export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:动态库绝对路径
     保存并退出
    . /etc/profile
     或
    source /etc/profile
     使之生效
    2.更改/etc/ld.so.cache文件列表
     通过sudo vim /etc/ld.so.conf间接修改
     插入动态库绝对路径
     保存并退出
    sudo ldconfig更新

Makefile

Makefile文件定义了一系列规则来指定文件的编译顺序、是否重新编译或者更复杂的功能操作。Makefile一旦写好,只需要make命令,整个工程就能完全自动化编译。

  1. 文件命名
    makefile 或者Makefile
  2. Makefile规则
    一个Makefile文件中可以有一个或多个规则
目标...:依赖...
	命令(Shell命令)
	...
目标:最终要生成的文件(伪目标除外)
依赖:生成目标所需要的文件或目标
命令:通过执行命令对依赖操作生成目标(命令前必须Tab缩进)

Makefile中的其它规则一般都是为第一条规则服务的
例子:

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
  1. 工作原理
    命令执行前,检查规则中的依赖是否存在
     若存在,执行命令
     若不存在,向下检查其它的规则,有没有一个规则是用来生成这个依赖的,
          若找到,则执行该规则中的命令
    检测更新,在执行规则中的命令时,会比较目标和依赖文件的时间
     若依赖的更新时间比目标的时间晚,需要重新生成目标
     若依赖的更新时间比目标的时间早,目标不需要更新,对应规则中的命令不需要被执行
  2. 变量
    自定义变量
      变量名=变量值  var=hello
    预定义变量
      AR:归档维护程序的名称,默认值为ar
      CC:C编译器的名称,默认值为cc
      CXX:C++编译器的名称,默认值为g++
     自动变量(只能在规则的命令中使用)
      $@:获取目标的完整名称
      $<:获取第一个依赖文件的名称
      $^:获取所有的依赖文件
    获取变量的值
      $(变量名)
  3. 模式匹配
    %.o:%.c
      %:通配符,匹配一个字符串
      两个%匹配的时同一个字符串
    例子(优化上个例子):
#定义变量
src=sub.o add.o mult.o div.o main.o
target=app
$(target):$(src)
	$(CC) $(src) -o $(target)
%.o:%.c
	$(CC) -c $< -o $@
  1. 函数
    $(wildcard PATTERN…)
     功能:获取指定目录下指定类型的文件列表
     参数:PATTERN指的是某个或多个目录下的对应的某种类型的文件,如果有多个目录,一般使用空格间隔
     返回:得到的若干个文件的文件列表,文件名之间使用空格间隔
     示例:
      $(wildcard *.c ./sub/*.c)
      返回值格式:a.c b.c c.c d.c e.c f.c
    $(patsubst <PATTERN>,<replacement>,<text>)
     功能:查找text中的单词是否符合模式pattern,如果匹配,则以replacement替换
     示例:
      $(patsubst %.c, %.o, x.c bar.c)
      返回值格式:x.o bar.o
    例子(优化上个例子):
#定义变量
#文件中有sub.c add.c mult.c div.c main.c
src=$(wildcard ./*.c)
objs=$(patsubst %.c, %.o, $(src))
target=app
$(target):$(objs)
	$(CC) $(objs) -o $(target)
%.o:%.c
	$(CC) -c $< -o $@
#删除依赖,.o文件
#将clean变成伪目标,不生成特定文件
.PHONY:clean
clean:
	rm $(objs) -f

make
再clean
make clean

GDB调试

为调试而编译时,关闭编译器优化选项,打开调试选项(-g),在尽量不影响程序行为的情况下打开所有warning(-Wall)
gcc -g -Wall program.c -o program
   -g选项的作用是在可执行文件中加入源代码的信息,
     比如可执行文件中第几条机器指令对应源代码的第几行。
     所以在调试时必须保证gdb能找到源代码文件。
启动:
  gdb 可执行程序
退出:
  quit/q
给程序设置参数:
  set args 10 20
获取设置参数:
  show args
GDB使用帮助:
  help
查看当前文件代码:
 从默认位置(main)显示:
  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
向下单步调试(遇到函数进入函数体):
  s/step
  finish跳出函数体(内无断点)
变量操作:
  p/print 变量名打印变量值
  ptype 变量名打印变量类型
自动变量操作:
  display 变量名自动打印指定变量的值
  i/info display
  undisplay 编号
其它操作:
  set var 变量名=变量值
  until跳出循环(内无断点)

标准C库IO函数和Linux系统IO函数对比

标准c库io有缓冲区,适合磁盘读写
  在man 3中查找
Linux的io无缓冲区,适合网络通信
  在man 2中查找
标准c库io调用Linux的io
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

虚拟地址空间

每个进程有一个虚拟地址空间
CPU中的MMU(Memory Management Unit),即内存管理单元,将虚拟地址空间映射到物理内存中
在这里插入图片描述

文件描述符

在这里插入图片描述

open函数

//需要头文件
#include<sys/types.h>
#include<sys/stat.h>
#include<fcntl.h>
//函数原型:
//打开一个已经存在的文件
int open(const char *pathname, int flags);
/*
	参数:
		-pathname:要打开的文件路径
		-flags:对文件的操作权限设置还有其他的设置
		  -必选项:O_RDONLY, O_WRONLY, O_RDWR  这三个设置是互斥的
		  -可选项:O_APPEND(追加),O_ASYNC(同步)。。。
	返回值:
		返回一个新的文件描述符,如果调用失败,返回-1(并改errno)
	
	errno:属于Linux系统函数库,库里面的一个全局变量,记录的是最近的错误号
	#include<stdio.h>
	void perror(const char *s);
		s参数:用户描述,比如hello,最终输出内容:hello:xxx(实际的错误描述)
	作用:打印errno对应的错误描述
*/
//创建一个新的文件
int open(const char *pathname, int flags, mode_t mode);
/*
	参数:
		-pathname:要创建的文件路径
		-flags:对文件的操作权限设置还有其他的设置
		  -必选项:O_RDONLY, O_WRONLY, O_RDWR  这三个设置是互斥的
		  -可选项:O_CREAT(若文件不存在,则创建新文件)
		-mode:八进制的数,表示创建出的的新文件的操作权限,rwxrwxrwx
			最终的权限是:mode & ~umask
			umask的作用是抹去某些权限,使之更合理
			比如mode为0777,umask为002,则最终0775,抹去了其他组的写权限
		
		flags参数是一个int类型的数据,占4个字节,32位。
		flags 32个位,每一位就是一个标志位,所有用 |
*/
//此处不是重载,而是使用了可变参数

打开文件

#include<sys/types.h>
#include<sys/stat.h>
#include<fcntl.h>
#include<stdio.h>
#include<unistd.h>//close函数需要
int main()
{
	//打开文件
	int fd = open("a.txt", O_RDONLY);
	if(fd == -1)
	{
		perror("open");
	}
	//关闭文件
	close(fd);
	return 0;
}

创建文件

#include<sys/types.h>
#include<sys/stat.h>
#include<fcntl.h>
#include<stdio.h>
#include<unistd.h>//close函数需要
int main()
{
	//创建文件
	int fd = open("create.txt", O_RDWR | O_CREAT, 0777);
	if(fd == -1)
	{
		perror("create");
	}
	//关闭文件
	close(fd);
	return 0;
}

read、write函数

//需要头文件
#include<unistd.h>
//函数原型
ssize_t read(int fd, void *buf, size_t count);
/*
	参数:
		-fd:文件描述符,open得到,通过这个文件描述符操作文件
		-buf:需要读取数据存放的地方,数组的地址(传出参数)
		-count:指定的数组的大小
	返回值:
		-成功:
			>0:返回实际的读取到的字节数
			=0:文件已经读取完了
		-失败:
			-1,并设置errno
*/
ssize_t write(int fd, const void *buf, size_t count);
/*
	参数:
		-fd:文件描述符,open得到,通过这个文件描述符操作文件
		-buf:要往磁盘写入的数据,数组
		-count:要写的数据的实际的大小
	返回值:
		-成功:
			>0:实际写入的字节数
			=0:表示没有写任何东西
		-失败:
			-1,并设置errno
*/

实例拷贝文件:

#include<unistd.h>
#include<stdio.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<fcntl.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("cpy.txt", O_WRONLY|O_CREAT, 0664);
	if(destfd == -1)
	{
		perror("create");
		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);
	return 0;
}

lseek函数

//需要头文件
#include<sys/types.h>
#include<unistd.h>
off_t lseek(int fd, off_t offset, int whence);
/*
	参数:
		-fd:文件描述符,open得到,通过这个文件描述符操作文件
		-offset:偏移量
		-whence:
			SEEK_SET:
				设置偏移量:文件头+第二个参数offset的值
			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<unistd.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<fcntl.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;
}

stat、lstat函数

//需要头文件
#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;
}

实现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指令
//输入:  ./ls a.txt
//输出:  -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;
}

文件属性操作函数

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);
access

//需要头文件
#include<unistd.h>
//函数原型
int access(const char *pathname, int mode);
/*
	作用:判断某个文件是否有某个权限,或者判断文件是否存在
	参数:
		-pathname:判断的文件路径
		-mode:
			R_OK:判断是否有读权限
			W_OK:判断是否有写权限
			X_OK:判断是否有执行权限
			F_OK:判断文件是否存在
	返回值:
		成功:0
		失败:-1并改errno
*/

示例:

#include<unistd.h>
#include<stdio.h>
int main()
{
	int ret=access("a.txt",F_OK);
	if(ret==-1) 
	{	
		perror("access");
		return -1;
	}
	printf("文件存在!!\n");
	return 0;
}

chmod

//需要头文件
#include<sys/stat.h>
//函数原型
int chmod(const char *pathname,mode_t mode);
/*
	作用:修改文件权限
	参数:
		-pathname:修改的文件路径
		-mode:需要修改的权限值,8进制的数(会&掩码)
	返回值:
		成功:0
		失败:-1并改errno
*/

示例

#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
修改所有者或所在组

truncate

//需要头文件
#include<sys/types.h>
#include<unistd.h>
//函数原型
int truncate(const char *path, off_t length);
/*
	作用:缩减或者扩展文件的尺寸至指定大小
	参数:
		-path:需要修改的文件的路径
		-length:需要最终文件变成的大小
	返回值:
		成功:0
		失败:-1并改errno
*/

示例:

#include<sys/types.h>
#include<unistd.h>
int main()
{
	int ret = truncate("b.txt",20);
	if(ret == -1) 
	{
		perror("truncate");
		return -1;
	}
	return 0;
}

目录操作函数

int mkdir(const char *pathname, mode_t mode);
int rmdir(const char *pathname);
int rename(const char *oldpath, const char *newpath);
int chdir(const char *path);
char *getcwd(char *buf, size_t size);
mkdir

//需要头文件
#include<sys/stat.h>
#include<sys/types.h>
//函数原型
int mkdir(const char *pathname, mode_t mode);
/*
	作用:创建一个目录
	参数:
		-pathname:创建的目录的路径
		-mode:权限,八进制的数(会&掩码)
	返回值:
		成功:0
		失败:-1并改errno
*/

示例

#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;
}

rmdir
删除目录函数

rename
重命名函数

chdir

//需要头文件
#include<unistd.h>
//函数原型
int chdir(const char *path);
/*
	作用:修改进程的工作目录
		比如在/home/jeon 启动了一个可执行程序a.out,进程的工作目录为/home/jeon
	参数:
		path:需要修改的工作目录
*/

getcwd

//需要头文件
#include<unistd.h>
//函数原型
char *getcwd(char *buf, size_t size);
/*
	作用:获取当前的工作目录
	参数:
		-buf:存储的路径,指向的时一个数组,传出参数
		-size:数组的大小
	返回值:
		返回的指向的一块内存,这个数据就是第一个参数
*/

示例:

#include<unistd.h>
#include<stdio.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<fcntl.h>
int main()
{
	//获取当前的工作目录
	char buf[128];
	getcwd(buf,sizeof(buf));
	printf("当前的工作目录是:%s\n",buf);

	//修改工作目录
	int ret = chdir("/home/jeon/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;
}

目录遍历函数

DIR *opendir(const char *name);
struct dirent *readdir(DIR *dirp);
int closedir(DIR *dirp);
opendir

//需要头文件
#include<sys/types.h>
#include<dirent.h>
//函数原型
DIR *opendir(const char *name);
/*
	作用:打开一个目录
	参数:
		name:需要打开的目录的名称
	返回值:
		DIR * 类型,理解为目录流
		失败则返回NULL
*/

readdir

//需要头文件
#include<dirent.h>
//函数原型
struct dirent *readdir(DIR *dirp);
/*
	作用:读取目录流中的数据
	参数:
		dirp:opendir()返回的结果
	返回值:
		struct dirent *:代表读取到的文件的信息
		读取到了末尾或者失败了,返回NULL,并设置errno
*/
struct dirent
{
	//此目录进入点的inode
	ino_t d_ino;
	//目录文件开头至此目录进入点的位移
	off_t d_off;
	//d_name的长度,不包含NULL字符
	unsigned short int d_reclen;
	//d_name所指的文件类型
	unsigned char d_type;
	//文件名
	char d_name[256];
};
  d_type:
	DT_BLK		-块设备
	DT_CHR		-字符设备
	DT_DIR		-目录
	DT_LNK		-软连接
	DT_FIFO		-管道
	DT_REG		-普通文件
	DT_SOCK		-套接字
	DT_UNKNOWN	-未知

closedir

//需要头文件
#include<sys/types.h>
#include<dirent.h>
//函数原型
int closedir(DIR *dirp);
/*
	作用:关闭目录
*/

示例
统计指定目录下普通文件的个数

#define _DEFAULT_SOURCE //使用DT_DIR等宏需要
#include<sys/types.h>
#include<dirent.h>
#include<stdio.h>
#include<string.h>
#include<stdlib.h>//exit(0);需要
//输入:  ./a.out lesson15
//输出:  此目录下所有普通文件的个数

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;
}

dup、dup2函数

int dup(int oldfd);复制文件描述符
int dup2(int oldfd, int newfd);重定向文件描述符
dup

//需要头文件
#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<sys/types.h>
#include<sys/stat.h>
#include<fcntl.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

//需要头文件
#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/types.h>
#include<sys/stat.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
	char* str="hello world";
	int len = write(fd1,str,strlrn(str));
	if(len == -1)
	{
	 	perror("write");
	 	return -1;
	}
	
	printf("fd:%d,fd1:%d,fd2:%d\n",fd,fd1,fd2);
	//fd2与fd1相同
	close(fd);
	close(fd1);
	
	return 0;
}

fcntl函数

int fcntl(int fd, int cmd, .../*arg*/);
复制文件描述符
设置/获取文件的状态标志

//需要头文件
#include<unistd.h>
#include<fcntl.h>
//函数原型
int fcntl(int fd, int cmd, .../*arg*/);
/*
	参数:
		-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(非阻塞)等可修改
*/

示例:

#include<unistd.h>
#include<fcntl.h>
#include<stdio.h>
#include<string.h>
int main()
{
	//1.复制文件描述符
	int fd0 = open("2.txt",O_RDONLY);
	int ret0 = fcntl(fd0,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,给flag加入O_APPEND这个标记
	//原先的必选项不可修改,所以得|
	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;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值