stat() 可以通过文件名获取文件的属性。
fstat() 可以通过打开的文件描述符获取文件的属性。
lstat() 和 stat() 功能相同,有一点区别就是当 pathname 是一个符号链接文件的时候,lstat() 返回的是符号链接文件本身的属性,而不是链接文件指向的文件的属性。而 stat() 则是返回符号链接所指向文件的属性。
#include
#include
#include
int stat(const char *pathname, struct stat *statbuf);
int fstat(int fd, struct stat *statbuf);
int lstat(const char *pathname, struct stat *statbuf);
下面是 stat 结构体中的部分成员:
struct stat {
dev_t st_dev; /* ID of device containing file */
ino_t st_ino; /* Inode number */
mode_t st_mode; /* File type and mode */
nlink_t st_nlink; /* Number of hard links */
uid_t st_uid; /* User ID of owner */
gid_t st_gid; /* Group ID of owner */
dev_t st_rdev; /* Device ID (if special file) */
off_t st_size; /* Total size, in bytes */
blksize_t st_blksize; /* Block size for filesystem I/O */
blkcnt_t st_blocks; /* Number of 512B blocks allocated */
struct timespec st_atim; /* Time of last access */
struct timespec st_mtim; /* Time of last modification */
struct timespec st_ctim; /* Time of last status change */
}
我们可以通过 stat 结构体中的 st_size 成员获取文件长度,程序 flen.c 如下:
#include
#include
#include
#include
#include
off_t flen(char *path){
struct stat fstat;
if (stat(path, &fstat) < 0) {
perror("stat()");
exit(1);
}
return fstat.st_size;
}
int main(int argc, char * argv[])
{
if (argc < 2) {
perror("flen ");
exit(1);
}
off_t fsize = flen(argv[1]);
printf("file size: %lld\n", (long long)fsize);
exit(0);
}
但 st_size 仅仅是文件的属性,实际占用磁盘的块大小和个数是 st_blksize 和 st_blocks。如下图,文件 flen.c 的 Size 为 429 B,但是占用磁盘块数为 8个,8 * 512B = 4096B:
下面是一段创建空洞文件的代码 hole_file.c,该程序会创建一个大小为 5g 的空洞文件(注意 5LL * 1024LL * 1024LL * 1024LL 一定要加单位 LL,否则计算结果会溢出):
#include
#include
#include
#include
#include
#include
int main(int argc, char *argv[])
{
if (argc < 2) {
perror("hole_file ");
exit(1);
}
int fd = open(argv[1], O_RDWR | O_CREAT);
if(fd < 0){
perror("open");
exit(1);
}
/* make a 5g hole file */
lseek(fd, 5LL * 1024LL * 1024LL * 1024LL, SEEK_SET);
write(fd, "", 1);
exit(0);
}
创建空洞文件 tmp 后使用 stat 查看其信息,可以看到 size 为 5g,但是实际在磁盘上仅占用了 8 个块,一共 4kb:
使用 ls -l 查看文件的权限信息可以得到如下结果,第一位用于指出文件的类型,后 9 位分别指出 文件所有者的权限、同组用户的权限以及其他人的权限。这些内容存储在 stat 结构体的 st_mode 成员中,st_mode 是一个 16 位的位图,用于表示文件类型,文件访问权限,及特殊权限位。
文件类型分为如下几种:
umask() 函数可以设置文件创建时的权限,防止产生权限过松的文件。并且返回之前的 mask 值。
#include
#include
mode_t umask(mode_t mask);
文件的权限位中,1 对应 x(执行),2 对应 w(可写),4 对应 r(可读),我们可以通过 chmod 指令来修改文件的权限位信息,如下:
Linux 操作系统也提供了 chmod() 和 fchmod() 函数来使得我们能在程序中修改文件的权限。
chmod() 可以通过文件路径修改文件的权限。
fchmod() 可以通过打开的文件描述符来修改文件权限。
#include
int chmod(const char *pathname, mode_t mode);
int fchmod(int fd, mode_t mode);
文件系统:文件或数据的存储和管理。
FAT16/32:静态单链表,闭源,惧怕大文件。
UFS:分区,位图,inode,块。
面试题:统计无符号整数二进制中 1 的个数。
去一法:
#include
int main()
{
unsigned int n;
scanf("%u\n", &n);
int count = 0;
while (n != 0) {
n = n & (n-1);
count++;
}
printf("一的个数为:%d\n", count);
return 0;
}
运行结果:
使用 ln 命令创建文件 file 的硬链接文件:
然后使用 stat 命令查看两个文件的信息:
可以发现两个文件的 inode 号是相同的,并且硬链接数 links 数变成了 2。由此可以知,硬链接在目录下创建一个目录项,该目录项指向的 inode 与被链接的文件的 inode 相同,所以两个文件 file 和 file_link 对应的是同一个磁盘上的文件,两者本质上是同一个文件。
硬链接是目录项的同义词,并且建立硬链接有限制,不能跨分区建立,不能给目录建立。
然后再使用 ln -s 来创建 file 的符号链接:
使用 stat 查看符号链接文件的信息,可以发现其指向的 inode 和原文件 file 不同,并且 links 为 1:
符号链接可跨分区,可给目录建立。
其实符号链接类似于 windows 中的快捷方式,相当于重新创建了一个新文件,文件内容为被链接文件的路径,使用 readlink -f 可以查看符号链接本身的内容:
unlink 系统调用可以删除文件名所指向文件的硬链接,如果删除后文件硬链接数为 0,则文件被删除(从磁盘上删除),如果删除后硬链接数不为 0,则文件不被删除。
#include
int unlink(const char *pathname);
另外,unlink 可以帮助我们创建一个临时文件,在程序中先打开一个新文件,然后立刻用 unlink 删除文件的硬链接,但此刻文件不会立刻被删除(因为进程打开了这个文件,链接数不为 0),进程结束后该文件才被释放。
真正实现删除文件的函数是 remove(rm 命令就是用这个封装的):
#include
int remove(const char *pathname);
mkdir 系统调用可以创建一个目录。
#include
#include
int mkdir(const char *pathname, mode_t mode);
rmdir 系统调用可以删除一个空目录。
#include
int rmdir(const char *pathname);
chdir 系统调用可以更改当前进程的工作目录(cd 命令)。
#include
int chdir(const char *path);
int fchdir(int fd);
getcwd 系统调用可以获取当前工作目录(pwd 命令)。
#include
char *getcwd(char *buf, size_t size);
glob(3) 函数可以找到所有与 pattern 所匹配的路径名(pattern 中一般包含通配符,如 * ? 等)。
#include
int glob(const char *pattern, int flags,
int (*errfunc) (const char *epath, int eerrno),
glob_t *pglob);
void globfree(glob_t *pglob);
结构体 glob_t 存放返回结果,如下,gl_pathc 和 gl_pathv 类似于 argc 和 argv,gl_pathc 存放路径名的个数,gl_pathv 存放路径名的地址。
typedef struct {
size_t gl_pathc; /* Count of paths matched so far */
char **gl_pathv; /* List of matched pathnames. */
size_t gl_offs; /* Slots to reserve in gl_pathv. */
} glob_t;
例子(glob.c):使用 glob 函数找出当前目录下的所有 .c 文件,注意参数不能从 shell 中传,只能在程序内部定义,因为 shell 在传值之前会先进行通配符转换(如将 *.c 变成实际的文件路径)。
#include
#include
#include
static int errfunc(const char *epath, int eerrno)
{
printf("%s\n", epath);
return 0;
}
int main()
{
glob_t pglob;
if (glob("./*.c", 0, NULL, &pglob) != 0) {
perror("glob");
exit(1);
}
for (int i = 0; i < pglob.gl_pathc; i++) {
printf("%s\n", pglob.gl_pathv[i]);
}
globfree(&pglob);
exit(0);
}
运行结果如下:
opendir(3)、closedir(3) 可以打开和关闭目录流。
#include
#include
DIR *opendir(const char *name);
DIR *fdopendir(int fd);
int closedir(DIR *dirp);
在通过 opendir(3) 打开目录后,可以使用 readdir(3) 函数通过 DIR 指针来读目录项。
#include
struct dirent *readdir(DIR *dirp);
返回的 struct dirent 成员如下,并且存储在静态区中。
struct dirent {
ino_t d_ino; /* Inode number */
off_t d_off; /* Not an offset; see below */
unsigned short d_reclen; /* Length of this record */
unsigned char d_type; /* Type of file; not supported
by all filesystem types */
char d_name[256]; /* Null-terminated filename */
};
例子(readdir.c):打印对应目录下的所有文件的名字。
#include
#include
#include
#include
int main(int argc, char *argv[])
{
if (argc < 2) {
perror("Usage: readdir ");
exit(1);
}
DIR *dp;
struct dirent *dirent_p;
if ((dp = opendir(argv[1])) == NULL) {
perror("opendir");
exit(1);
}
while ((dirent_p = readdir(dp)) != NULL) {
printf("%s\n", dirent_p->d_name);
}
exit(0);
}
rewinddir(3) 函数可以重置目录流 dirp 至目录的开头处。这样的话再使用 readdir(3) 函数就会读取第一个目录项。
#include
#include
void rewinddir(DIR *dirp);
seekdir(3) 函数可以设置下一次 readdir(3) 开始读取的位置,其中的 loc 参数必须是 telldir(3) 函数返回的值。
#include
void seekdir(DIR *dirp, long loc);
telldir(3) 函数返回当前目录流的位置。
#include
long telldir(DIR *dirp);
下面是 Ubuntu 下的 /etc/passwd 文件的内容,其格式如下:
用户名:加密后的口令:用户id:用户所在组id:注释字段:home目录位置:登录shell
getpwuid(3) 可以通过用户的 uid 获取用户的信息。
getpwnam(3) 可以通过用户的名称 name 获取用户信息。
#include
#include
struct passwd *getpwnam(const char *name);
struct passwd *getpwuid(uid_t uid);
struct passwd 结构体的定义如下(在
struct passwd {
char *pw_name; /* username */
char *pw_passwd; /* user password */
uid_t pw_uid; /* user ID */
gid_t pw_gid; /* group ID */
char *pw_gecos; /* user information */
char *pw_dir; /* home directory */
char *pw_shell; /* shell program */
};
/etc/group 文件可以获取组的信息,getgrgid(3) 和 getgrnam(3) 两个函数也可以获取组信息。
/etc/shadow 文件可以查看用户经过 hash 后的密码。
获取时间戳,time(2) 系统调用,gtime(3),localtime(3) 等。
正常终止:
异常终止:
main 函数的返回值是给其父进程看的,如在 shell 中执行程序,其父进程为 shell,使用 echo $? 即可查看 shell 上一个程序执行的返回状态。
exit(3) 函数可以让进程正常终止。
#include
void exit(int status);
atexit(3):钩子函数,进程正常终止时,被 atexit(3) 注册的函数会逆序调用(与注册顺序相反),类似于 C++ 的析构函数。
#include
int atexit(void (*function)(void));
如下面的程序:
先打印 begin 和 end,直到程序 exit(0) 正常终止后,再依次调用 f3、f2、f1 函数:
要注意的是,在直接调用 _exit(2) 系统调用的时候,是不会执行钩子函数和 IO 清理的。而 exit(3) 则会依次执行,如下图。
getopt(3)、getopt_long(3) 分别可以获取 “-” 和 “--” 后的命令行参数。
C 程序的存储空间布局分为以下五个部分:
pmap 命令可以展示进程的内存分布图。
浅谈静态库和动态库 - 知乎
setjmp()、longjmp()。