1- 系统调用关系


C标准函数和系统函数调用关系:一个helloworld打印到屏幕的过程

7e944c3d949676fb8ac9f2ba96f75927941290awfj

2- 文件描述符


2.1 PCB进程控制块

b8dae3ba4e2b75e1cf44af5b3ir3wjfiow

用来描述进程的标志,本质上是一个结构体,其中有一根指针指向文件描述符表。

2.2 文件描述符表

714cb4a726fuj4out0fg9wu03

文件描述符表里面的成员都是一个指针,本质是key-value键值对,value就是指针。

文件描述符是指向一个文件结构体的指针!当使用open等函数打开的文件,就是返回的fd

操作系统并不会暴露给你文件描述符实现细节,所以只暴露了下标。

文件描述符从3开始,0是标准输入,1是标准输出,2是标准错误,能打开的文件最大1024个.

STDIN_FILENO 0  // 标准输入
STDOUT_FILENO 1 // 标准输出
STDERR_FILENO 2 // 标准错误

2.3 最大打开文件数

  • 一个进程默认打开文件的个数 1024

  • 命令查看 ulimit -a 查看 open files 对应值。默认为 1024

  • 可以使用 ulimit -n 4096 修改

  • 当然也可以通过修改系统配置文件永久修改该值,但是不建议这样操作。

  • cat /proc/sys/fs/file-max 可以查看该电脑最大可以打开的文件个数。受内存大小影响。

3- open/close函数


3.1 函数原型

#include <unistd.h>     // 包含open/close函数
#include <fcntl.h>      // 包含O_RDONLY等宏定义
​
int open(const char *pathname, int flags);
int open(const char *pathname, int flags, mode_t mode);
int close(int fd);

可以使用man 2 openman 2 close查看相关函数的man手册。

  • pathname:要打开的文件的路径名。

  • flags:打开文件的标志,以下是一些常用的flags标志量:

    • O_RDONLY:以只读模式打开文件。

    • O_WRONLY:以只写模式打开文件。

    • O_RDWR:以读写模式打开文件。

    • O_CREAT:如果文件不存在,则创建文件。

    • O_TRUNC:如果文件存在并且以写入模式打开,则将文件截断为零长度。

    • O_APPEND:如果文件以写入模式打开,则在文件末尾追加数据。

    • O_NONBLOCK: 用于设置文件描述符为非阻塞模式。

    • O_EXCL:与 O_CREAT 一起使用时,如果文件已经存在,则打开失败。

    • O_BINARY:以二进制模式打开文件(在某些系统上可用)。

    • O_TEXT:以文本模式打开文件(在某些系统上可用)。

    这些标志可以通过按位或运算符 | 进行组合,以满足特定的需求。例如,要以读写模式打开文件并在文件末尾追加数据,可以使用 flags 参数为 O_RDWR | O_APPEND

  • mode:创建新文件时的权限(仅在 flags 包含 O_CREAT 时有效)。0775,0511等

    创建文件时,指定文件访问权限。权限同时受 umask 影响。结论为: ​ 文件权限 = mode & ~umask

  • fd:要关闭的文件描述符。

3.2 常见错误

  • 打开文件时可能出现的错误:

    • 文件不存在(如果未指定 O_CREAT 标志)。

    • 权限不足。

    • 文件路径错误或无效。

    • 文件已经被其他进程以独占方式打开。

  • 关闭文件时可能出现的错误:

    • 文件描述符无效或不可关闭。

    • 关闭文件时发生了错误(例如,文件已经关闭或文件描述符无效)。

可以使用 errnostrerror 来获取错误信息,并根据需要采取适当的措施来处理错误。

3.3 示例

/** 实现了打开文件文件的程序,如果文件不存在则按照权限创建,如果存在则文件清0 **/
​
#include <unistd.h>     // 包含open/close函数
#include <stdio.h>
#include <fcntl.h>      // 包含O_RDONLY等宏定义
#include <errno.h>      // 包含了errno参数
#include <string.h>     // str化errno参数
​
int main(int argc, char *argv[])
{
    int fd;
    fd =  open("./test.txt",O_RDONLY | O_CREAT | O_TRUNC, 0777);
    printf("fd = %d\nerror = %d:%s\n",fd,errno,strerror(errno));
    // errno是一个全局变量,当函数发生错误时,设置一个errno的值指示具体的错误
    // strerror 是一个函数,用于将错误代码转换为对应的错误消息字符串。它接受一个整数参数,即错误代码,然后返回相应的错误消息字符串。
    close(fd);
    return 0;
}

4- read/write函数


4.1 函数原型

#include <unistd.h>     // 包含read/write函数
​
ssize_t read(int fd, void *buf, size_t count);
ssize_t write(int fd, const void *buf, size_t count);

可以使用man 2 readman 2 write查看相关函数的man手册。

  • fd:文件描述符,表示要进行读写操作的文件或套接字。文件描述符是一个非负整数,可以通过打开文件或创建套接字来获得。

  • buf:用于读取或写入数据的缓冲区的指针。对于 read,它是用于存储读取数据的缓冲区;对于 write,它是包含要写入的数据的缓冲区。

  • count:要读取或写入的字节数。它指定了缓冲区的大小。

4.2 常见错误

这些函数返回一个 ssize_t 类型的值,表示实际读取或写入的字节数。如果返回值为负数,则表示出现了错误。如果返回值为0,表示读到了文件末尾。一些常见的错误代码包括:

  • EINTR:操作被信号中断。

  • EAGAINEWOULDBLOCK:非阻塞文件描述符上没有可用的数据或无法立即写入数据。

  • EBADF:无效的文件描述符。

  • EFAULT:缓冲区指针无效。

  • EINVAL:无效的参数,如读取或写入的字节数为负数。

  • EIO:I/O 错误。

可以使用 errnostrerror 来获取错误信息,并根据需要采取适当的措施来处理错误。

4.3 预读入缓输出机制

03e59072e58ec5da40debf3cbd029bbbdoping23rf

buf的大小决定了一次write函数从用户空间向内核空间传输的字节数,kernel空间的缓冲区最大大小为4KB,即4096。因为write函数从用户空间访问内核空间比较耗时,所以buf越大,可以减少访问kernel的次数,这样可以提高效率。

4.4 示例

/***** 实现了拷贝文件的程序 *****/
​
#include <unistd.h>         // 包含open/close/read/write函数
#include <stdio.h>          // 包含标准输入输出函数的头文件,printf、scanf、fopen、fclose 等函数
#include <stdlib.h>         // 包含标准库函数的头文件。malloc、free、atoi、rand、qsort 等函数。
#include <fcntl.h>          // 包含O_RDONLY等宏定义
#include <errno.h>          // 包含了errno参数
#include <string.h>         // str化errno参数
#include <pthread.h>        // 包含 POSIX 线程库的头文件。线程的创建、销毁、同步、互斥等操作的函数和数据类型。
​
int main(int argc, char *argv[])
{
    // 定义大小
    int n = 0;
    char buf[1024];     // buf大小设定为1024,即一次向内核空间传输1KB大小的数据
    
    int fd1 = open(argv[1],O_RDONLY);   // read
    if(fd1 == -1)
    {
        perror("open argv1 error!");    // 打印错误输出信息
        exit(1);
    }
    int fd2 = open(argv[2],O_RDWR | O_CREAT | O_TRUNC, 0664);// write
    if(fd2 == -1)
    {
        perror("open argv2 error!");
        exit(1);
    }
    
    while((n=read(fd1,buf,1024))!=0)
    {
        if(n<0)
        {
            perror("read error!");
            break; 
        }
        write(fd2,buf,n);   // 第三个参数必须为n
    }
    
    close(fd1);
    close(fd2);
    return 0;
}

5- 错误处理函数


5.1 perror 函数

void perror(const char *s);

示例:

#include <stdio.h>
#include <errno.h>
​
int main() {
    FILE *file = fopen("nonexistent.txt", "r");
    if (file == NULL) {
        perror("Error");
        return 1;
    }
​
    // 其他文件操作
​
    fclose(file);
    return 0;
}

如果fopen打开文件失败,则perror 函数将打印类似以下的错误消息到标准错误流:

Error: No such file or directory.

5.2 strerror 函数

char *strerror(int errnum); 

示例:

#include <stdio.h>
#include <string.h>
#include <errno.h>

int main() {
    int errnum = errno;  // 获取当前的错误代码
    const char *errmsg = strerror(errnum);  // 获取错误消息字符串

    printf("Error message: %s\n", errmsg);

    return 0;
}

使用 errno 宏获取当前的错误代码,并将其作为参数传递给 strerror 函数。然后,我们将返回的错误消息字符串打印出来。

需要注意的是,strerror 函数返回的错误消息字符串是一个静态的全局数组,因此在多 线程环境下使用时需要注意线程安全性。

6- 阻塞与非阻塞(等待)


产生阻塞的场景:读设备文件、网络文件。(读常规文件无阻塞概念)

/dev/tty -- 终端文件。

open("/dev/tty", O_RDWR | O_NONBLOCK) --- 设置 /dev/tty 非阻塞状态。(默认为阻塞状态)

/***** 阻塞 *****/
/***** 该程序读取设备文件 *****/
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <errno.h>

int main(void)
{
	char buf[10];
	int n;

	n = read(STDIN_FILENO,buf,10);
	if(n<0)
	{
		perror("read STDIN_FILENO");
		exit(1);
	}
	
	write(STDOUT_FILENO,buf,n);
	
	return 0;
}

/***** 非阻塞 *****/
/***** 更改非阻塞读取终端——超时设置 *****/
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <errno.h>
#include <fcntl.h>
#include <string.h>

#define MSG_TRY "Try Again\n"
#define MSG_TIMEOUT "TIME OUT\n"

int main(void)
{
	char buf[10];
	int fd,n,i;
	fd = open("/dev/tty", O_RDONLY | O_NONBLOCK);	// 设置为非阻塞状态,默认为阻塞状态
	if(fd <0)
	{
		perror("open /dev/tty");
		exit(1);
	}
	printf("open /dev/tty ok... %d\n",fd);

	for(i = 0; i < 5; i++)
	{
		n = read(fd,buf,10);
		if(n > 0)
		{
			break;
		}
		
		if(errno != EAGAIN)		// EWOULDBLOCK
		{
			perror("READ /DEV/TTY");
			exit(1);
		}
		else
		{
			write(STDOUT_FILENO,MSG_TRY,strlen(MSG_TRY));
			sleep(2);
		}
	}
	
	if (i == 5)
	{
		write(STDOUT_FILENO,MSG_TIMEOUT,strlen(MSG_TIMEOUT));
	}
	else
	{
		write(STDOUT_FILENO,buf,n);
	}

	close(fd);
	
	return 0;
}

7- fcntl函数


改变一个已经打开的文件的访问控制属性

7.1 函数原型

#include <unistd.h>
#include <fcntl.h>

int fcntl(int fd, int cmd, ... /* arg */);

7.2 参数

  • fd : 文件描述符

  • cmd : 命令,决定了后续参数个数

    • F_GETFL: 获取文件状态

    • F_SETFL: 设置文件状态

    • F_DUPFD: 复制文件描述符并返回一个新的文件描述符

  • ... /* arg */ : 可变参数,根据cmd的不同有不同的形式

7.3 返回值

  • int类型的返回值,根据cmd的不同有不同的返回值

  • flgs |= O_NONBLOCK

  • fcntl(fd, F_SETFL, flgs);

7.4 示例

终端文件默认是阻塞读的,这里用fcntl将其更改为非阻塞读

#include <unistd.h>  
#include <fcntl.h>  
#include <errno.h>  
#include <stdio.h>  
#include <stdlib.h>  
#include <string.h>  
  
#define MSG_TRY "try again\n"  
  
int main(void)  
{  
    // 将标准输入更改为非阻塞
    char buf[10];  
    int flags, n;  
  
    flags = fcntl(STDIN_FILENO, F_GETFL); //获取stdin属性信息  
    if(flags == -1){  
        perror("fcntl error");  
        exit(1);  
    }  
    flags |= O_NONBLOCK;  
    int ret = fcntl(STDIN_FILENO, F_SETFL, flags);  
    if(ret == -1){  
        perror("fcntl error");  
        exit(1);  
    }  

// 测试
tryagain:  
    n = read(STDIN_FILENO, buf, 10);  
    if(n < 0){  
        if(errno != EAGAIN){          
            perror("read /dev/tty");  
            exit(1);  
        }  
        sleep(3);  
        write(STDOUT_FILENO, MSG_TRY, strlen(MSG_TRY));  
        goto tryagain;  
    }  
    write(STDOUT_FILENO, buf, n);  
  
    return 0;  
}  

8- lseek函数


Linux 中可使用系统函数 lseek 来修改文件偏移量(读写位置)

8.1 函数原型

#include <unistd.h> 
#include <sys/types.h>

off_t lseek(int fd, off_t offset, int whence);

8.2 参数

  • fd:文件描述符

  • offset: 偏移量,就是将读写指针从whence指定位置向后偏移offset个单位,矢量值

  • whence:起始偏移位置

8.3 返回值

成功: 较起始位置偏移量

失败: -1 errno

8.4 使用场景

  • 文件的读、写使用同一偏移位置

  • 使用lseek获取文件大小

  • 使用lseek拓展文件大小,要想使文件大小真正拓展,必须引起IO操作

8.5 示例

写一个句子到空白文件,完事调整光标位置,读取刚才写那个文件。

​
#include <stdio.h>  
#include <stdlib.h>  
#include <unistd.h>  
#include <string.h>  
#include <fcntl.h>  
  
int main(void)  
{  
    int fd, n;  
    char msg[] = "It's a test for lseek\n";  
    char ch;  
  
    fd = open("lseek.txt", O_RDWR|O_CREAT, 0644);  
    if(fd < 0){  
        perror("open lseek.txt error");  
        exit(1);  
    }  
  
    write(fd, msg, strlen(msg));    //使用fd对打开的文件进行写操作,此时读写位置位于文件结尾处。  
  
    lseek(fd, 0, SEEK_SET);         //修改文件读写指针位置,位于文件开头。 注释该行会怎样呢?  
  
    while((n = read(fd, &ch, 1))){  
        if(n < 0){  
            perror("read error");  
            exit(1);  
        }  
        write(STDOUT_FILENO, &ch, n);   //将文件内容按字节读出,写出到屏幕  
    }  
  
    close(fd);  
  
    return 0;  
}  

8.6 拓展文件大小

eef96132ecc4e6261c137f70dd56e1davjweopjgio43

8.7 总结

  • 对于写文件再读取那个例子,由于文件写完之后未关闭,读写指针在文件末尾,所以不调节指针,直接读取不到内容。

  • lseek读取的文件大小总是相对文件头部而言。

  • lseek读取文件大小实际用的是读写指针初末位置的偏移差,一个新开文件,读写指针初位置都在文件开头。如果用这个来扩展文件大小,必须引起IO才行,于是就至少要写入一个字符。上面代码出现lseek返回799ls查看为800的原因是,lseek读取到偏移差的时候,还没有写入最后的‘$’符号,末尾那一大堆^@,是文件空洞,如果自己写进去的也想保持队形,就写入“\0”。

truncate函数

可以直接拓展文件。

int ret = truncate("dict.cp", 250);     // 该文件必须存在,扩展出来250大小,成功返回0

9- 传入传出参数


传入参数:

1. 指针作为函数参数。

2. 同常有const关键字修饰。

3. 指针指向有效区域, 在函数内部做读操作。

传出参数:

1. 指针作为函数参数。

2. 在函数调用之前,指针指向的空间可以无意义,但必须有效。

3. 在函数内部,做写操作。

4。函数调用结束后,充当函数返回值。

传入传出参数:

1. 指针作为函数参数。

2. 在函数调用之前,指针指向的空间有实际意义。

3. 在函数内部,先做读操作,后做写操作。

4. 函数调用结束后,充当函数返回值

10- 目录项和inode


inode.png

一个文件主要由两部分组成,dentry(目录项)和inode

inode本质是结构体,存储文件的属性信息,如:权限、类型、大小、时间、用户、盘快位置…

也叫做文件属性管理结构,大多数的inode都存储在磁盘上。

少量常用、近期使用的inode会被缓存到内存中。

所谓的删除文件,就是删除inode,但是数据其实还是在硬盘上,以后会覆盖掉。

11- stat函数


获取文件属性,(从inode结构体中获取)

11.1 函数原型

stat/lstat函数

int stat(const char *path, struct stat *buf);

11.2 参数

  • path: 文件路径

  • buf:(传出参数) 存放文件属性,inode结构体指针。

  • 获取文件大小: buf.st_size

  • 获取文件类型: buf.st_mode

  • 获取文件权限: buf.st_mode

11.3 返回值

  • 成功: 0

  • 失败: -1 errno

11.4 示例

获取文件大小的正规军解法,用stat

#include <stdio.h>  
#include <stdlib.h>  
#include <unistd.h>  
#include <string.h>  
#include <pthread.h>
#include <sys/stat.h>

int main(int argc, char *argv[])
{
    struct stat sbuf;

    int ret = stat(argv[1], &sbuf);
    if(ret == -1)
    {
        perror("stat error");
        exit(1);
    }

    printf("file size: %ld\n", sbuf.st_size);

    return 0;
}

12- lstat和stat


lstat查看文件类型

#include <stdio.h>  
#include <stdlib.h>  
#include <unistd.h>  
#include <string.h>  
#include <pthread.h>
#include <sys/stat.h>
​
int main(int argc, char *argv[])
{
    struct stat sbuf;
​
    int ret = stat(argv[1], &sbuf);
    if(ret == -1)
    {
        perror("stat error");
        exit(1);
    }
​
    if (S_ISREG(sbuf.st_mode))
    {
        printf("It is a regular\n");
    }
    else if (S_ISDIR(sbuf.st_mode))
    {
        printf("It is a dir\n");
    }
    else if (S_ISFIFO(sbuf.st_mode))
    {
        printf("It is a pipe\n");
    }
​
    // stat存在符号穿透, 显示的是符号链接的文件
    else if (S_ISLNK(sbuf.st_mode))
    {
        printf("It is a sym link\n");   // 无法显示符号连接
    }  
​
    return 0;
}

stat存在符号穿透, 显示的是符号链接的文件

类似穿透现象还有cat vim(实现基于系统调用)

不想穿透符号就用lstat

a0c78401ed7b249ba30914f57597013weitu

13- link和unlink和隐式回收


  • 硬链接数就是dentry数目

  • link就是用来创建硬链接的

  • link可以用来实现mv命令

13.1 link 函数

int link(const char *oldpath, const char *newpath);

用这个来实现mv,用oldpath来创建newpath,然后删除oldpath就行。

13.2 unlink函数

删除一个链接

int unlink(const char *pathname);
  • unlink是删除一个文件的目录项dentry,使【硬链接数-1】

  • unlink函数的特征:清除文件时,如果文件的硬链接数到0了,没有dentry对应,但该文件仍不会马上被释放,要等到所有打开文件的进程关闭该文件,系统才会挑时间将该文件释放掉。

13.3 示例:使用unlink实现mv功能

#include <stdio.h>  
#include <stdlib.h>  
#include <string.h>  
#include <unistd.h>
#include <pthread.h>

// 实现mv (rename) 功能
int main(int argc, char *argv[])
{
    link(argv[1], argv[2]);

    unlink(argv[1]);
}

13.4 示例:验证unlink是删除dentry

/* 
 *unlink函数是删除一个dentry 
 */  
#include <unistd.h>  
#include <fcntl.h>  
#include <stdlib.h>  
#include <string.h>  
#include <stdio.h>  
  
  
int main(void)  
{  
    int fd, ret;  
    char *p = "test of unlink\n";  
    char *p2 = "after write something.\n";  
  
    fd = open("temp.txt", O_RDWR|O_CREAT|O_TRUNC, 0644);  
    if(fd < 0){  
        perror("open temp error");  
        exit(1);  
    }  
  
    ret = write(fd, p, strlen(p));  
    if (ret == -1) {  
        perror("-----write error");  
    }  
  
    printf("hi! I'm printf\n");  
    ret = write(fd, p2, strlen(p2));  
    if (ret == -1) {  
        perror("-----write error");  
    }  
  
    printf("Enter anykey continue\n");  
    getchar();  
  
    ret = unlink("temp.txt");        //具备了被释放的条件  
    if(ret < 0){  
        perror("unlink error");  
        exit(1);  
    }  
  
    close(fd);  
  
    return 0;  
}  

编译程序并运行,程序阻塞,文件还未被删除,因为当前程序未结束。输入字符使当前进程结束后,temp.txt就不见了。

下面开始搞事,在程序中加入段错误成分,段错误在unlink之前,由于发生段错误,程序后续删除temp.txtdentry部分就不会再执行,temp.txt就保留了下来,这是不科学的。

解决办法是检测fd有效性后,立即释放temp.txt,由于进程未结束,虽然temp.txt的硬链接数已经为0,但还不会立即释放,仍然存在,要等到程序执行完才会释放。这样就能避免程序出错导致临时文件保留下来。

因为文件创建后,硬链接数立马减为0,即使程序异常退出,这个文件也会被清理掉。这时候的内容是写在内核空间的缓冲区。

以下是修改后的程序

/* 
 *unlink函数是删除一个dentry 
 */  
#include <unistd.h>  
#include <fcntl.h>  
#include <stdlib.h>  
#include <string.h>  
#include <stdio.h>  
  
  
int main(void) {
    int fd, ret;  
    char *p = "test of unlink\n";  
    char *p2 = "after write something.\n";  
  
    fd = open("temp.txt", O_RDWR|O_CREAT|O_TRUNC, 0644);  
    if(fd < 0){  
        perror("open temp error");  
        exit(1);  
    }  
  
    ret = unlink("temp.txt");        //具备了被释放的条件  
    if(ret < 0){  
        perror("unlink error");  
        exit(1);  
    }  
  
    ret = write(fd, p, strlen(p));  
    if (ret == -1) {  
        perror("-----write error");  
    }  
  
    printf("hi! I'm printf\n");  
    ret = write(fd, p2, strlen(p2));  
    if (ret == -1) {  
        perror("-----write error");  
    }  
  
    printf("Enter anykey continue\n");  
    getchar();  
  
    close(fd);  
  
    return 0;  
}  

隐式回收

当进程结束运行时,所有进程打开的文件会被关闭,申请的内存空间会被释放。系统的这一特性称之为隐式回收系统资源。

比如上面那个程序,要是没有在程序中关闭文件描述符,没有隐式回收的话,这个文件描述符会保留,多次出现这种情况会导致系统文件描述符耗尽。所以隐式回收会在程序结束时收回它打开的文件使用的文件描述符。

14- readlink函数


读取符号链接文件本身内容,得到链接所指向的文件名。

ssize_t readlink(const char *path, char *buf, size_t bufsize); 
​
// 成功: 0;
// 失败: -1; 设置errno为相应值

15- 文件目录rwx权限差异


dir-rwx.png

如果使用vi编辑器打开目录,则会得到目录项的列表

16- 目录操作函数


16.1 opendir函数

根据传入的目录名打开一个目录(库函数) DIR*类似于FILE*

#include <dirent.h>
​
DIR * opendir(char *name);
​
// 成功: 返回指向该目录的结构体指针
// 失败: 返回NULL

参数支持相对路径绝对路径两种方式

例如:

打开当前目录:

1. getcwd(), opendir()
2. opendir(".")

16.2 closedir函数

关闭打开的目录

#include <dirent.h>

int closedir(DIR *dirp)

// 成功: 返回0
// 失败: 返回-1,设置errno为相应值

16.3 readdir函数

#include <dirent.h>

struct dirent *readdir(DIR * dirp);

以下为struct dirent结构体

struct dirent {
    ino_t          d_ino;       // inode number
    off_t          d_off;       // offset to the next dirent
    unsigned short d_reclen;    // length of this record
    unsigned char  d_type;      // type of file
    char           d_name[256]; // filename
};

示例:

打印指定目录的目录项

#include <stdio.h>
#include <stdlib.h>
#include <dirent.h>

int main(int argc, char *argv[]) {
    DIR *dir;
    struct dirent *entry;

    dir = opendir(argv[1]); // 打开目录
    if (dir == NULL) {
        perror("opendir");
        return 1;
    }

    while ((entry = readdir(dir)) != NULL) { // 读取目录中的条目
        // 用来隐藏'.'和'..'
        if(strcmp((entry->d_name),".") == 0)
            continue;
        if(strcmp((entry->d_name),"..") == 0)
        	continue;
        
        printf("%s\t", entry->d_name); // 打印文件名
    }
    printf("\n");

    closedir(dir); // 关闭目录

    return 0;
}

17- 递归遍历目录


17.1 递归遍历目录思路

任务需求: 使用opendir closedir readdir stat实现一个递归遍历目录的程序

输入一个指定目录,默认为当前目录。递归列出目录中的文件,同时显示文件大小。

思路分析:递归遍历目录:ls -R .

  1. 判断命令行参数,获取用户要查询的目录名。

    int argc, char *argv[1]
  2. 判断用户指定的是否是目录。

    stat S_ISDIR();		---> // 将这个代码封装在isFile()函数中
  3. 读目录

    opendir (dir)
    while (readdir ())
    {
    	判断isFile()
    	-
    	---true---普通文件,直接打印
    	-
    	---false--是目录---sprintf
    					 --拼接目录访问绝对路径---> sprintf(path, "%s/%s", dir, d_name)
    					 --继续判断是否为文件,是则打印,否则继续进入子目录
    					 --进入目录,递归调用自己---> opendir(path) readdir closedir
    }
    closedir()

17.2 代码预览

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/stat.h>
#include <dirent.h>
#include <pthread.h>

void isFile(char *name);

// 打开目录读取,处理目录
void read_dir(char *dir, void (*func)(char *))
{
    DIR *dp;
    char path[256]; 
    struct dirent *sdp;

    // 打开dir
    dp = opendir(dir);
    if (dp == NULL)
    {
        perror("opendir error");
        return;
    }

    // 读dir
    while((sdp = readdir(dp)) != NULL)
    {
        // 跳过"."和"..",防止无限递归
        if (strcmp(sdp->d_name,".") == 0 || strcmp(sdp->d_name,"..") == 0)
        {
            continue;
        }

        // 拼接路径(目录项本身不可访问)
        sprintf(path, "%s/%s", dir, sdp->d_name);

        // 递归
        // isFile(path);

        // 回调函数
        (*func)(path);

    }

    // 关闭dir
    closedir(dp);

    return;
}

void isFile(char *name)
{
    int ret = 0;
    struct stat sb;
    ret = stat(name, &sb);

    if (ret == -1)
    {
        perror("stat error");
        return;
    }

    if (S_ISDIR(sb.st_mode))
    {
        read_dir(name, isFile);
    }
    printf("%10s\t\t%ld\n",name, sb.st_size);

    return;
}

int main(int argc, char *argv[])
{
    // 判断命令行参数
    if (argc == 1)
    {
        isFile(".");    // 如果没有输入命令行参数,则执行当前目录
    }
    else
    {
        isFile(argv[1]);
    }

    return 0;
}

18- 重定向


用来做重定向,本质就是复制文件描述符

18.1 dup函数

函数原型

int dup(int oldfd);		// 文件描述符复制

参数

  • oldfd: 已有文件描述符

返回值

  • 成功: int类型的新文件描述符,这个描述符和oldfd指向相同内容。

  • 失败: 返回-1,设置errno为相应值。

示例

给一个旧的文件描述符,返回一个新文件描述符

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>
#include <pthread.h>

int main(int argc, char *argv[])
{
    int fd = open(argv[1], O_RDONLY);
    int newfd = dup(fd);
    printf("newfd = %d\n", newfd);

    return 0;
}

18.2 dup2函数

函数原型

int dup2(int oldfd, int newfd);		// 文件描述符复制,oldfd拷贝给newfd

参数

  • oldfd: 已有文件描述符

  • newfd: 新文件描述符

返回值

  • 成功: int类型的新文件描述符,这个描述符和oldfd指向相同内容。

  • 失败: 返回-1,设置errno为相应值

示例

将一个已有文件描述符fd1复制给另一个文件描述符fd2,然后用fd2修改fd1指向的文件

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>
#include <pthread.h>
​
int main(int argc, char *argv[])
{
    int fd1 = open(argv[1], O_RDWR);
    int fd2 = open(argv[2], O_RDWR);
​
    int fdret = dup2(fd1, fd2);
    printf("newfd = %d\n", fdret);
​
    int ret = write(fd2, "i love you\n", 12);   // 这样写会覆盖原有的内容
    printf("ret = %d\n", ret);
​
    return 0;
}

也可以进行标准输出的重定向,将输出到STDOUT的内容重定向到文件里

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>
#include <pthread.h>
​
int main(int argc, char *argv[])
{
    int fd1 = open(argv[1], O_RDWR);
    int fd2 = open(argv[2], O_RDWR);
​
    int fdret = dup2(fd1, fd2);
    printf("newfd = %d\n", fdret);
​
    int ret = write(fd2, "1234567", 7);     // 这样写会覆盖文件原有的内容
    printf("ret = %d\n", ret);
​
    dup2(fd1, STDOUT_FILENO);
    printf("---------- I Love You ! \n");   // 这样写会覆盖文件原有的内容
    
    return 0;
}

该程序的重定向状态如下:

dup.PNG

18.3 fcntl实现dup描述符

函数原型

#include <unistd.h>
#include <fcntl.h>
​
int newfd = fcntl(oldfd, F_DUPFD, minfd);  // 用于描述符复制的函数形式

参数

  • oldfd : 旧文件描述符

  • F_DUPFD : 复制文件描述符并返回一个新的文件描述符的命令选项

  • minfd : 新文件描述符的最小值

返回值

  • int类型的新文件描述符。新的文件描述符是系统中当前可用的最小的未使用的文件描述符,并且大于或等于 minfd

示例

fcntl来实现描述符的复制

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>
#include <pthread.h>
​
int main(int argc, char *argv[])
{
    int fd1 = open(argv[1], O_RDWR);
    printf("fd1 = %d\n", fd1);
​
    // 使用方法:int newfd = fcntl(oldfd, F_DUPFD, minfd);
    int newfd = fcntl(fd1, F_DUPFD,0);      // 0被占用,fcntl使用文件描述符表中最小可用的文件描述符返回
    printf("newfd = %d\n", newfd);
​
    int newfd2 = fcntl(fd1, F_DUPFD,7);     // 7未被占用,返回 >=7 的文件描述符
    printf("newfd2 = %d\n", newfd2);
    
    return 0;
}