文件系统
1 目录和文件
1.1 获取文件属性信息stat
相关函数
-
stat() 得到file指向的文件并将其属性回填到buf中,面对符号链接文件时获取的是所指向的目标文件的属性
/* Get file attributes for FILE and put them in BUF. */ extern int stat (const char *__restrict __file, struct stat *__restrict __buf)
-
fstat() 得到fd指向的文件并将其属性回填到buf中
/* Get file attributes for the file, device, pipe, or socket that file descriptor FD is open on and put them in BUF. */ extern int fstat (int __fd, struct stat *__buf)
-
lstat() 得到file指向的文件并将其属性回填到buf中,面对符号链接文件时获取的符号链接文件的属性
/* Get file attributes about FILE and put them in BUF. If FILE is a symbolic link, do not follow it. */ extern int lstat (const char *__restrict __file, struct stat *__restrict __buf)
stat结构体
struct stat {
dev_t st_dev; /* ID of device containing file 包含当前文件的设备ID号*/
ino_t st_ino; /* Inode number Inode号*/
mode_t st_mode; /* File type and mode 文件的权限信息*/
nlink_t st_nlink; /* Number of hard links 硬链接数*/
uid_t st_uid; /* User ID of owner userID*/
gid_t st_gid; /* Group ID of owner groupID*/
dev_t st_rdev; /* Device ID (if special file) 设备ID*/
off_t st_size; /* Total size, in bytes 文件大小*/
blksize_t st_blksize; /* Block size for filesystem I/O 一个block有多大*/
blkcnt_t st_blocks; /* Number of 512B blocks allocated 当前文件占了多少个block*/
/* Since Linux 2.6, the kernel supports nanosecond
precision for the following timestamp fields.
For the details before Linux 2.6, see NOTES. */
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 最后一次修改亚数据的时间*/
#define st_atime st_atim.tv_sec /* Backward compatibility */
#define st_mtime st_mtim.tv_sec
#define st_ctime st_ctim.tv_sec
};
- st_mode,是一个16位的位图,用于表示文件类型,文件访问权限,及特殊权限位
- 文件类型共有7中dcb-lsp,可以用如下7个宏定义的函数来进行判断
Because tests of the above form are common, additional macros are defined by POSIX to allow the test of the file type in st_mode to be written more concisely:
S_ISREG(m) is it a regular file? 常规文件
S_ISDIR(m) directory? 目录
S_ISCHR(m) character device? 字符设备
S_ISBLK(m) block device? 块设备
S_ISFIFO(m) FIFO (named pipe)? 命名管道
S_ISLNK(m) symbolic link? (Not in POSIX.1-1996.) 符号链接
S_ISSOCK(m) socket? (Not in POSIX.1-1996.) 套接字
umask
umask = 0002; 0666&0002
作用:防止产生权限过松的文件
1.2 文件权限的更改/管理
-
chmod() 更改file文件的权限
/* Set file access permissions for FILE to MODE. If FILE is a symbolic link, this affects its target instead. */ extern int chmod (const char *__file, __mode_t __mode)
-
fchmod() 更改一个成功打开的文件fd的权限
/* Set file access permissions of the file FD is open on to MODE. */ extern int fchmod (int __fd, __mode_t __mode) __THROW
1.3 文件系统
文件系统的作用:文件或数据的存储和管理
1.3.1 FAT文件系统
本质就是一个静态存储的单链表,所以FAT文件系统承载的文件的大小是有上限的,N值有限,如图将一块磁盘分为N个char[SIZE]大小的数据块,用于存储数据,next用于存储指针信息,比如下图中有一个File需要用到三个数据块,且分配的数据块是1,3,7,这是在next数组中,File指向next[1],next[1]指向next[3],next[3]指向next[7],通过访问next数据就能直到File使用了那几个数据块从而访问到File的数据
1.3.2 UFS文件系统
1.4 硬链接和符号链接
硬链接
-
shell命令
ln source source1
-
作用:
硬链接产生的source1可以认为就是给源文件起了一个别名,也就是说,两个文件名指向同一个inode,也就是同一个文件,对硬链接source1进行的操作打开source也能看见;用stat命令查看source1的属性值时可以发现source1是普通文件,同时links硬链接数增加,对于一个文件,其硬链接
符号链接
-
shell命令
ln -s source source1
-
符号链接
符号链接产生的source1是一个新文件,通过stat查看source1属性可以看到source1是一个符号链接文件,这个文件本身不存储source的内容信息,这个文件指向source文件,通过打开source1文件,可以访问到source文件,可以理解为win下的快捷方式。
相关函数
-
link() 系统调用,给一个文件起一个新的文件名,对应shell的ln命令
/* Make a link to FROM named TO. */ extern int link (const char *__from, const char *__to)
-
unlink() 系统调用,删除硬链接,至于这个文件是否被删除,需要看两个方面:1,硬链接数为0了;2没有任何文件描述符指向这个文件,不能删除空目录
/* Remove the link NAME. */ extern int unlink (const char *__name)
通过unlink(),可以构造临时文件,比如先调用tmpnam()创建一个临时文件名,然后open(),之后立马调用unlink()即可创建一个临时文件
-
remove() 删除一个一个文件或目录,不能删除空目录,对应shell的rm命令
/* Remove file FILENAME. */ extern int remove (const char *__filename)
-
rename() 系统调用,移动文件或修改文件名,即shell中的mv命令
/* Rename file OLD to NEW. */ extern int rename (const char *__old, const char *__new)
1.5 目录的创建和销毁/更改工作路径
- mkidr() 系统调用,创建一个path目录并且指定权限,对应shell中的mkdir命令
/* Create a new directory named PATH, with permission bits MODE. */
extern int mkdir (const char *__path, __mode_t __mode)
- rmdir() 系统调用,删除一个空目录,对应shell中的rmdir命令
/* Remove the directory PATH. */
extern int rmdir (const char *__path)
- chdir() 系统调用,更改当前进程的工作路径到path,对应shell中的cd命令
/* Change the process's working directory to PATH. */
extern int chdir (const char *__path)
/* Change the process's working directory to the one FD is open on. */
extern int fchdir (int __fd)
- getcwd() 系统调用,获取当前进程的工作路径
/* Get the pathname of the current working directory,
and put it in SIZE bytes of BUF. Returns NULL if the
directory couldn't be determined or SIZE was too small.
If successful, returns BUF. In GNU, if BUF is NULL,
an array is allocated with `malloc'; the array is SIZE
bytes long, unless SIZE == 0, in which case it is as
big as necessary. */
extern char *getcwd (char *__buf, size_t __size)
1.6 分析目录/读取目录内容
1.6.1 glob()函数
作用,分析一个pattern,flags表示特殊要求
/* Do glob searching for PATTERN, placing results in PGLOB.
The bits defined above may be set in FLAGS.
If a directory cannot be opened or read and ERRFUNC is not nil,
it is called with the pathname that caused the error, and the
`errno' value from the failing call; if it returns non-zero
`glob' returns GLOB_ABEND; if it returns zero, the error is ignored.
If memory cannot be allocated for PGLOB, GLOB_NOSPACE is returned.
Otherwise, `glob' returns zero. */
#if !defined __USE_FILE_OFFSET64
extern int glob (const char *__restrict __pattern, int __flags,
int (*__errfunc) (const char * epath, int eerrno),
glob_t *__restrict __pglob) __THROW;
/* Free storage allocated in PGLOB by a previous `glob' call. */
extern void globfree (glob_t *__pglob) __THROW;
#else
- flags如下:
/* Bits set in the FLAGS argument to `glob'. */
#define GLOB_ERR (1 << 0)/* Return on read errors. */
#define GLOB_MARK (1 << 1)/* Append a slash to each name. */
#define GLOB_NOSORT (1 << 2)/* Don't sort the names. */
#define GLOB_DOOFFS (1 << 3)/* Insert PGLOB->gl_offs NULLs. */
#define GLOB_NOCHECK (1 << 4)/* If nothing matches, return the pattern. */
#define GLOB_APPEND (1 << 5)/* Append to results of a previous call. */
#define GLOB_NOESCAPE (1 << 6)/* Backslashes don't quote metacharacters. */
#define GLOB_PERIOD (1 << 7)/* Leading `.' can be matched by metachars. */
#if !defined __USE_POSIX2 || defined __USE_MISC
# define GLOB_MAGCHAR (1 << 8)/* Set in gl_flags if any metachars seen. */
# define GLOB_ALTDIRFUNC (1 << 9)/* Use gl_opendir et al functions. */
# define GLOB_BRACE (1 << 10)/* Expand "{a,b}" to "a" "b". */
# define GLOB_NOMAGIC (1 << 11)/* If no magic chars, return the pattern. */
# define GLOB_TILDE (1 << 12)/* Expand ~user and ~ to home directories. */
# define GLOB_ONLYDIR (1 << 13)/* Match only directories. */
# define GLOB_TILDE_CHECK (1 << 14)/* Like GLOB_TILDE but return an error
if the user name is not available. */
# define __GLOB_FLAGS (GLOB_ERR|GLOB_MARK|GLOB_NOSORT|GLOB_DOOFFS| \
GLOB_NOESCAPE|GLOB_NOCHECK|GLOB_APPEND| \
GLOB_PERIOD|GLOB_ALTDIRFUNC|GLOB_BRACE| \
GLOB_NOMAGIC|GLOB_TILDE|GLOB_ONLYDIR|GLOB_TILDE_CHECK)
#else
# define __GLOB_FLAGS (GLOB_ERR|GLOB_MARK|GLOB_NOSORT|GLOB_DOOFFS| \
GLOB_NOESCAPE|GLOB_NOCHECK|GLOB_APPEND| \
GLOB_PERIOD)
#endif
- errfunc 函数指针,当解析出错的时候通过这个函数来处理出错路径和errno
- 解析出错时还可以通过查当前函数的返回值来判断出了什么错
/* Error returns from `glob'. */
#define GLOB_NOSPACE 1 /* Ran out of memory. */
#define GLOB_ABORTED 2 /* Read error. */
#define GLOB_NOMATCH 3 /* No matches found. */
#define GLOB_NOSYS 4 /* Not implemented. */
globfree()
/* Free storage allocated in PGLOB by a previous `glob' call. */
extern void globfree (glob_t *__pglob)
glob_t结构体
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;
示例
static int errFunc(const char * errPath, int errno) {
puts(errPath);
fprintf(stderr, "ERROR MSG:%s\n", strerror(errno));
return 0;
}
int main(int argc, char **argv) {
const char *pat = "/etc/*";//匹配etc目录下的所有文件
glob_t globres;
int err, i;
err = glob(pat, 0, errFunc, &globres);
if (err) {
printf("Error code = %d\n", err);
exit(1);
}
for (i = 0; i < globres.gl_pathc; i++) {
puts(globres.gl_pathv[i]);
}
globfree(&globres);
exit(0);
}
1.6.2 目录函数
在linux中目录也是一个文件,当我们使用vim打开一个目录的时候就能够看到一个目录文件中的内容,目录流就是这个目录文件的流
opendir(3) 打开一个目录流,成功返回目录流,失败返回NULL并设置errno值
DIR *opendir(const char *name);
DIR *fdopendir(int fd);
closedir(3) 关闭一个目录流
/* Close the directory stream DIRP.
Return 0 if successful, -1 if not.
This function is a possible cancellation point and therefore not
marked with __THROW. */
extern int closedir (DIR *__dirp)
readdir(3) 该函数读取打开目录流中的一行内容并返回
- 相关定义
/* Read a directory entry from DIRP. Return a pointer to a `struct
dirent' describing the entry, or NULL for EOF or error. The
storage returned may be overwritten by a later readdir call on the
same DIR stream.
If the Large File Support API is selected we have to use the
appropriate interface.
This function is a possible cancellation point and therefore not
marked with __THROW. */
#ifndef __USE_FILE_OFFSET64
extern struct dirent *readdir (DIR *__dirp)
/* Reentrant version of `readdir'. Return in RESULT a pointer to the
next entry.
This function is a possible cancellation point and therefore not
marked with __THROW. */
extern int readdir_r (DIR *__restrict __dirp,
struct dirent *__restrict __entry,
struct dirent **__restrict __result)
struct dirent {
ino_t d_ino; /* Inode number */
off_t d_off; /* Not an offset; see below 距离下一个dirent的长度*/
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 */
};
- 示例
int main(int argc, char **argv) {
const char *pat = "/etc";
DIR * dir;
struct dirent *dt;
int cnt = 0;
dir = opendir(pat);
if (dir == NULL) {
perror("opendir()");
exit(1);
}
while ((dt = readdir(dir)) != NULL) {
puts(dt->d_name);
cnt++;
}
printf("%s目录下有%d个文件\n", pat, cnt);
closedir(dir);
exit(0);
}
rewinddir(3) 将目录流置到目录文件开始处
/* Rewind DIRP to the beginning of the directory. */
extern void rewinddir (DIR *__dirp)
seekdir(3) 目录流指向pos处
/* Seek to position POS on DIRP. */
extern void seekdir (DIR *__dirp, long int __pos)
telldir(3) 返回目录流当前的指向
/* Return the current position of DIRP. */
extern long int telldir (DIR *__dirp)
1.7 目录文件解析——du命令实现
使用glob()函数实现
static int isvalid(const char *path) {
path = strrchr(path, '/');
if (path == NULL) exit(1);
if (!strcmp(path + 1, ".") || !strcmp(path + 1, "..")) return 0;
return 1;
}
static int64_t mydu(const char *path) {
static const int PATHSIZE = 256;
static struct stat pst; //因为这个变量只在递归点之前,所以可以将其放到静态区中来节省栈空间
glob_t globres;
int err, i;
int64_t sum;
char nextpath[PATHSIZE];
if (lstat(path, &pst) < 0) {
perror("lstat()");
exit(1);
}
//1 非目录文件处理
if (!S_ISDIR(pst.st_mode)) {
return pst.st_blocks / 2;
}
//2 目录文件处理
//a 解析非隐藏文件
strncpy(nextpath, path, PATHSIZE);
strncat(nextpath, "/*", PATHSIZE);
if ((err = glob(nextpath, 0, NULL, &globres)) < 0) {
fprintf(stderr, "glob error num : %d\n", err);
exit(1);
}
//b 解析隐藏文件,追加到原先的globres中
strncpy(nextpath, path, PATHSIZE);
strncat(nextpath, "/.*", PATHSIZE);
if ((err = glob(nextpath, GLOB_APPEND, NULL, &globres)) < 0) {
fprintf(stderr, "glob error num : %d\n", err);
globfree(&globres);
exit(1);
}
sum = pst.st_blocks / 2;
for (i = 0; i < globres.gl_pathc; i++) {
if (isvalid(globres.gl_pathv[i])) {
sum += mydu(globres.gl_pathv[i]);
}
}
globfree(&globres);
return sum;
}
int main(int argc, char **argv) {
const int BUFSIZE = 256;
char buf[BUFSIZE];
if (argc < 2) {
fprintf(stderr, "Usage...\n");
exit(1);
}
printf("%ld\n", mydu(argv[1]));
exit(0);
}
使用目录函数实现
static int isvalid(const char *path) {
if (!strcmp(path, ".") || !strcmp(path, "..")) return 0;
return 1;
}
static int64_t mydu(const char *path) {
static const int PATHSIZE = 256;
static struct stat pst; //因为这个变量只在递归点之前,所以可以将其放到静态区中来节省栈空间
DIR *pdir;
struct dirent *dt;
int64_t sum = 0;
char nextpath[PATHSIZE];
if (lstat(path, &pst) < 0) {
perror("lstat()");
exit(1);
}
//1 非目录文件处理
if (!S_ISDIR(pst.st_mode)) {
return pst.st_blocks / 2;
}
//2 目录文件处理
pdir = opendir(path);
sum = pst.st_blocks / 2;
while ((dt = readdir(pdir)) != NULL) {
if (isvalid(dt->d_name)) { //这里注意拿到的dt->d_name不是绝对路径,是相对当前path的路径
strncpy(nextpath, path, PATHSIZE);
strncat(nextpath, "/", PATHSIZE);
strncat(nextpath, dt->d_name, PATHSIZE);
sum += mydu(nextpath);
}
}
closedir(pdir);
return sum;
}
int main(int argc, char **argv) {
const int BUFSIZE = 256;
char buf[BUFSIZE];
if (argc < 2) {
fprintf(stderr, "Usage...\n");
exit(1);
}
printf("%ld\n", mydu(argv[1]));
exit(0);
}
2 系统数据文件和信息
2.1 口令文件——用户数据库
口令文件即/etc/passwd目录下的文件
相关函数
-
getpwuid(3) 根据uid返回用户的详细信息
/* Search for an entry with a matching user ID. This function is a possible cancellation point and therefore not marked with __THROW. */ extern struct passwd *getpwuid (__uid_t __uid)
-
getpwnam(3) 根据用户名返回用户的详细信息
/* Search for an entry with a matching username. This function is a possible cancellation point and therefore not marked with __THROW. */ extern struct passwd *getpwnam (const char *__name)
-
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 */ };
示例
int main(int argc, char **argv) { struct passwd *user; if (argc < 2) { fprintf(stderr, "Usage...\n"); exit(1); } user = getpwuid(atoi(argv[1])); if (user == NULL) { fprintf(stderr, "Uid Not Found...\n"); exit(1); } printf("username = %s\n", user->pw_name); exit(0); }
2.2 组文件——组数据
/etc/group 目录下的文件就是组文件,记录了组相关的信息
相关函数
-
getgrgid(3) 通过gid获取组信息结构文件
/* Search for an entry with a matching group ID. This function is a possible cancellation point and therefore not marked with __THROW. */ extern struct group *getgrgid (__gid_t __gid);
-
getgrnam(3) 通过组名获取组信息结构文件
/* Search for an entry with a matching group name. This function is a possible cancellation point and therefore not marked with __THROW. */ extern struct group *getgrnam (const char *__name);
-
group结构体 保存了组结构信息
struct group { char *gr_name; /* group name */ char *gr_passwd; /* group password */ gid_t gr_gid; /* group ID */ char **gr_mem; /* NULL-terminated array of pointers to names of group members */ };
2.3 阴影口令文件
/etc/shadow 该文件至少包含用户名和加密口令,与该口令相关的其他信息也可以存储在该文件中,只有root用户有权限查看该文件,root用户的权限如下0640
相关函数
-
getspnam() 根据用户名获取其在/etc/shadow文件中对应的一行内容
/* Get shadow entry matching NAME. This function is not part of POSIX and therefore no official cancellation point. But due to similarity with an POSIX interface or due to the implementation it is a cancellation point and therefore not marked with __THROW. */ extern struct spwd *getspnam (const char *__name);
-
getspent() 获取下一行内容
/* Get next entry from database, perhaps after opening the file. This function is not part of POSIX and therefore no official cancellation point. But due to similarity with an POSIX interface or due to the implementation it is a cancellation point and therefore not marked with __THROW. */ extern struct spwd *getspent (void);
-
crypt() 返回通过口令原文和salt合并后得到加密后的串,该函数只能看到salt三个$前的内容
/* Encrypt at most 8 characters from KEY using salt to perturb DES. */ extern char *crypt (const char *__key, const char *__salt)
If salt is a character string starting with the characters "$id$" followed by a string optionally terminated by "$", then the result has the form: $id$salt$encrypted id identifies the encryption method used instead of DES and this then determines how the rest of the password string is interpreted. The following values of id are supported: ID | Method ───────────────────────────────────────────────────────── 1 | MD5 2a | Blowfish (not in mainline glibc; added in some | Linux distributions) 5 | SHA-256 (since glibc 2.7) 6 | SHA-512 (since glibc 2.7) Thus, $5$salt$encrypted and $6$salt$encrypted contain the password encrypted with, respectively, functions based on SHA-256 and SHA-512.
-
getpass() 将终端的回显功能去掉,prompt为提示符
/* Prompt with PROMPT and read a string from the terminal without echoing. Uses /dev/tty if possible; otherwise stderr and stdin. */ extern char *getpass (const char *__prompt)
-
spwd结构体
struct spwd { char *sp_namp; /* Login name */ char *sp_pwdp; /* Encrypted password */ long sp_lstchg; /* Date of last change (measured in days since 1970-01-01 00:00:00 +0000 (UTC)) */ long sp_min; /* Min # of days between changes */ long sp_max; /* Max # of days between changes */ long sp_warn; /* # of days before password expires to warn user to change it */ long sp_inact; /* # of days after password expires until account is disabled */ long sp_expire; /* Date when account expires (measured in days since 1970-01-01 00:00:00 +0000 (UTC)) */ unsigned long sp_flag; /* Reserved */ };
示例
int main(int argc, char **argv) {
char *pwd;
struct spwd *sd;
char *cp;
if (argc < 2) {
fprintf(stderr, "Usage...\n");
exit(1);
}
//输入密码
pwd = getpass("Please enter your password:");
//获取用户在shadow文件中内容
sd = getspnam(argv[1]);
cp = crypt(pwd, sd->sp_pwdp);
//判断密码是否正确
if (!strcmp(cp, sd->sp_pwdp)) {
puts("OK");
} else {
puts("FAILED");
}
exit(0);
}
2.4 时间戳
函数
-
time(2) 从内核中取时间戳(以秒为单位) 失败返回-1并设置errno值
/* Return the current time and put it in *TIMER if TIMER is not NULL. */ extern time_t time (time_t *__timer);
-
gmtime(3) 将time_t类型的时间转换成struct tm,内容以世界协调时间来存储
/* Return the `struct tm' representation of *TIMER in Universal Coordinated Time (aka Greenwich Mean Time). */ extern struct tm *gmtime (const time_t *__timer);
-
localtime(3) 将time_t类型的时间转换成struct tm,内容以本地时间来存储
/* Return the `struct tm' representation of *TIMER in the local timezone. */ extern struct tm *localtime (const time_t *__timer);
-
mktime(3) 将struct tm结构体中的时间转换成time_t类型的时间戳
/* Return the `time_t' representation of TP and normalize TP. */ extern time_t mktime (struct tm *__tp) ;
-
strftime(3) 将struct tm结构体中的时间转换成字符串
从tp中提取出来需要的字段(由format指定),并将其放在s缓冲区中(大小为maxsize)
/* Format TP into S according to FORMAT. Write no more than MAXSIZE characters and return the number of characters written, or 0 if it would exceed MAXSIZE. */ extern size_t strftime (char *__restrict __s, size_t __maxsize, const char *__restrict __format, const struct tm *__restrict __tp) __THROW;
struct tm结构体
该结构体存储在静态区
struct tm {
int tm_sec; /* Seconds (0-60) */
int tm_min; /* Minutes (0-59) */
int tm_hour; /* Hours (0-23) */
int tm_mday; /* Day of the month (1-31) */
int tm_mon; /* Month (0-11) */
int tm_year; /* Year - 1900 */
int tm_wday; /* Day of the week (0-6, Sunday = 0) */
int tm_yday; /* Day in the year (0-365, 1 Jan = 0) */
int tm_isdst; /* Daylight saving time */
};
实例
- 往一个文件中写时间
int main(int argc, char **argv) {
FILE *fp = NULL;
int lines = 0;
char buf[BUFSIZE];
time_t t_time;
struct tm *c_tm;
//打开文件
fp = fopen(FNAME, "a+");
if (fp == NULL) {
perror("fopen():");
exit(1);
}
//确认当前的行数
while (fgets(buf, BUFSIZE, fp) != NULL) {
lines++;
}
while (1) {
t_time = time(NULL);
if (t_time == -1) {
perror("time():");
}
c_tm = localtime(&t_time);
strftime(buf, BUFSIZE, "%Y-%m-%d %H:%M:%S", c_tm);
fprintf(fp, "%-4d %s\n", ++lines, buf);
//必须得刷新fp,因为fp是全缓冲的内容
fflush(fp);
sleep(1);
}
fclose(fp);
exit(0);
}
- 计算今天加上100天后是哪一天
int main(int argc, char **argv) {
time_t stamp;
struct tm *tp;
char buf[BUFSIZE];
stamp = time(NULL);
if (stamp == -1) {
perror("time():");
exit(1);
}
tp = localtime(&stamp);
strftime(buf, BUFSIZE, "Now: %Y-%m-%d", tp);
puts(buf);
tp->tm_mday += 100;
//该函数在tp合法的时候会直接进行转换,不合法时则将其转换为合法的
mktime(tp);
strftime(buf, BUFSIZE, "100 days later: %Y-%m-%d", tp);
puts(buf);
exit(0);
}
3 进程环境
3.1 进程终止
3.1.1 main函数
int main(int argc, char **argv);
3.1.2 进程终止
正常终止
-
从main函数返回
-
调用exit(3)函数
The exit() function causes normal process termination and the value of status & 0377 is returned to the parent (see wait(2)).
返回status的末八位,且是有符号的,相当于一个返回一个char
All functions registered with atexit(3) and on_exit(3) are called, in the reverse order of their registration.
在执行exit()前会先调用atexit()和on_exit()中注册的函数,并且注册的逆序调用
-
调用_exit(2)或_Exit(2),调用这两个函数不会调用钩子函数与IO清理
-
最后一个线程从其启动例程返回
-
最后一个线程调用pthread_exit()函数
异常终止
- 调用abort()
- 接到一个信号并终止,比如shell中ctrl+c
- 最后一个线程对其取消请求作出响应
atexit()钩子函数
作用:注册一个函数在进程正常终止的时候调用
int atexit(void (*function)(void));
static void f1(void) {
puts("f1() is working...");
}
static void f2(void) {
puts("f2() is working...");
}
static void f3(void) {
puts("f3() is working...");
}
int main(int argc, char **argv) {
puts("Begin!");
atexit(f1);
atexit(f2);
atexit(f3);
puts("End!");
exit(0);
}
执行效果:
Begin!
End!
f3() is working...
f2() is working...
f1() is working...
3.2 命令行参数分析
相关函数
-
getopt()
如果一个option被成功找到则返回这个option对应的字符,如果没有找到返回-1
- optstring后面跟的就是我们需要解析出来的字符,当某个字符后面会跟着修饰字段时,在其后面加上:,然后当我们拿到相应的有修饰的字符的时候,optarg就指向这个用来修饰的字符串
- 如果optstring的第一个字符是’-',就能识别非选项的传参,识别后返回值是-1
- optind表示下一个要读的argv的下标
int getopt(int argc, char * const argv[], const char *optstring); extern char *optarg; extern int optind, opterr, optopt;
-
getopt_long()
int getopt_long(int argc, char * const argv[], const char *optstring, const struct option *longopts, int *longindex);
示例:实现命令行输入指定模式后输出指定模式的时间串
/**
* -Y:year 0
* -m:month 1
* -d:day 2
* -H:hour 3
* -M:minute 4
* -S:second 5
*/
#define FMTSTRSIZE 1024
#define SIZE 6
int main(int argc, char **argv) {
FILE *fp = stdout;
time_t stamp;
struct tm *tp;
char buf[BUFSIZE];
int c, pre_flag = 0, post_flag = 0;
char fmtstr[FMTSTRSIZE];
fmtstr[0] = '\0';
stamp = time(NULL);
int map[2][SIZE] = {0};
if (stamp == -1) {
perror("time():");
exit(1);
}
//命令行分析
while (1) {
c = getopt(argc, argv, "-y:mdH:MS");
if (c < 0) break;
switch (c) {
case 1 : //表示读取到非选项的传参,这时往最后一个读取到的非选项传参表示的文件中写
fp = fopen(argv[optind - 1], "w");
if (fp == NULL) {
perror("fopen():");
fp = stdout;
}
break;
case 'y':
map[0][0] = 1;
if (strcmp(optarg, "4") == 0) {
map[1][0] = 1;
} else if (strcmp(optarg, "2") == 0) {
map[1][0] = 0;
} else {
fprintf(stderr, "Invalid argument of -y\n");
}
break;
case 'm':
map[0][1] = 1;
break;
case 'd':
map[0][2] = 1;
break;
case 'H':
if (strcmp(optarg, "24") == 0) {
map[1][3] = 1;
} else if (strcmp(optarg, "2")) {
map[1][3] = 0;
} else {
fprintf(stderr, "Invalid argument of -H\n");
}
map[0][3] = 1;
break;
case 'M':
map[0][4] = 1;
break;
case 'S':
map[0][5] = 1;
break;
default:
break;
}
}
//拼接格式串
if (map[0][0]) { //年的处理
if (map[1][0]) {
strncat(fmtstr, "%Y", FMTSTRSIZE);
} else {
strncat(fmtstr, "%y", FMTSTRSIZE);
}
pre_flag = 1;
}
if (map[0][1]) { //月的处理
if (pre_flag) {
strncat(fmtstr, "-%m", FMTSTRSIZE);
} else {
strncat(fmtstr, "%m", FMTSTRSIZE);
pre_flag = 1;
}
}
if (map[0][2]) { //日的处理
if (pre_flag) {
strncat(fmtstr, "-%d", FMTSTRSIZE);
} else {
strncat(fmtstr, "%d", FMTSTRSIZE);
pre_flag = 1;
}
}
if (map[0][3]) { //时的处理
if (pre_flag) {
if (map[1][3]) {
strncat(fmtstr, " %H", FMTSTRSIZE);
} else {
strncat(fmtstr, " %I(%P)", FMTSTRSIZE);
}
} else {
if (map[1][3]) {
strncat(fmtstr, "%H", FMTSTRSIZE);
} else {
strncat(fmtstr, "%I(%P)", FMTSTRSIZE);
}
}
post_flag = 1;
}
if (map[0][4]) { //分的处理
if (post_flag) {
strncat(fmtstr, ":%M", FMTSTRSIZE);
} else {
strncat(fmtstr, "%M", FMTSTRSIZE);
post_flag = 1;
}
}
if (map[0][5]) {
if (post_flag) {
strncat(fmtstr, ":%S", FMTSTRSIZE);
} else {
strncat(fmtstr, "%S", FMTSTRSIZE);
}
}
tp = localtime(&stamp);
strncat(fmtstr, "\n", BUFSIZE);
strftime(buf, BUFSIZE, fmtstr, tp);
fputs(buf, fp);
if (fp != stdout) fclose(fp);
exit(0);
}
3.3 环境变量
在shell中可以通过export命令来查看环境变量,环境变量的保存形式是key=value的格式,同时环境变量就可以理解成操作系统这个进程所使用的一个全局变量,保存在environ中
- 示例:打印出所有的环境变量
extern char **environ;
int main(int argc, char **argv) {
for (int i = 0; environ[i] != NULL; i++) {
puts(environ[i]);
}
exit(0);
}
相关函数
- getenv(3) 获取name对应的value值
/* Return the value of envariable NAME, or NULL if it doesn't exist. */
extern char *getenv (const char *__name) __THROW __nonnull ((1)) __wur;
- setenv(3) 改变(replace为真)或添加(replace为0)一个环境变量的值
/* Set NAME to VALUE in the environment.
If REPLACE is nonzero, overwrite an existing value. */
extern int setenv (const char *__name, const char *__value, int __replace)
__THROW __nonnull ((2));
- unsetenv(3) 删除一个环境变量
/* Remove the variable NAME from the environment. */
extern int unsetenv (const char *__name) __THROW __nonnull ((1));
- putenv(3)
/* The SVID says this is in <stdio.h>, but this seems a better place. */
/* Put STRING, which is of the form "NAME=VALUE", in the environment.
If there is no `=', remove NAME from the environment. */
extern int putenv (char *__string) __THROW __nonnull ((1));
3.4 进程空间
以32位系统为例
通过shell命令:pmap能够查看进程空间的地址内容
3.5 函数跳转
函数调用
函数调用的过程实际上就是一个压栈的过程,比如现有如下过程:
main()->a()->b()->c()->d()
main调用a时会在栈中保存自己的执行现场,然后产生一个a函数的栈帧;a调用b时a会保存自己的执行现场,然后产生一个b函数的栈帧,以此类推,现在有一个需求,在执行到d时得到某个想要的结果后就直接返回到a中(正常情况下会弹栈以此回复c->b->a->main的执行现场),这时goto可以帮助我们实现这样的功能,但是goto有一个问题不会恢复bc的执行现场,所以就引入了setjmp()和longjmp()函数来帮助我们解决这样的问题
相关函数
-
setjmp() 该函数将系统栈保存于envbuf中,以供以后调用longjmp()。当第一次调用setjmp(),它的返回值为0。之后调用longjmp(),longjmp()第二个参数即为setjmp()的返回值。
/* Do not save the signal mask. This is equivalent to the `_setjmp' BSD function. */ #define setjmp(env) _setjmp (env) /* Store the calling environment in ENV, not saving the signal mask. Return 0. */ extern int _setjmp (struct __jmp_buf_tag __env[1]) __THROWNL;
-
longjmp()
/* Jump to the environment saved in ENV, making the `setjmp' call there return VAL, or 1 if VAL is 0. */ extern void longjmp (struct __jmp_buf_tag __env[1], int __val) __THROWNL __attribute__ ((__noreturn__));
示例
static jmp_buf save;
static void d() {
printf("%s():Begin...\n", __FUNCTION__ );
printf("%s():Jump now!\n", __FUNCTION__ );
longjmp(save, 6);
printf("%s():End...\n", __FUNCTION__ );
}
static void c() {
printf("%s():Begin...\n", __FUNCTION__ );
printf("%s():Call d()...\n", __FUNCTION__ );
d();
printf("%s():d() returned...\n", __FUNCTION__ );
printf("%s():End...\n", __FUNCTION__ );
}
static void b() {
printf("%s():Begin...\n", __FUNCTION__ );
printf("%s():Call c()...\n", __FUNCTION__ );
c();
printf("%s():c() returned...\n", __FUNCTION__ );
printf("%s():End...\n", __FUNCTION__ );
}
static void a() {
int ret;
printf("%s():Begin...\n", __FUNCTION__ );
ret = setjmp(save);
if (ret == 0) { //说明是在设置跳转点
printf("%s():Call b()...\n", __FUNCTION__);
b();
printf("%s():b() returned...\n", __FUNCTION__);
} else { //说明是从其他地方跳转过来的
printf("%s():Jumped back here with code %d\n", __FUNCTION__, ret);
}
printf("%s():End...\n", __FUNCTION__ );
}
int main(int argc, char **argv) {
printf("%s():Begin...\n", __FUNCTION__ );
printf("%s():Call a()...\n", __FUNCTION__ );
a();
printf("%s():a() returned...\n", __FUNCTION__ );
printf("%s():End...\n", __FUNCTION__ );
exit(0);
}
输出结果:
main():Begin...
main():Call a()...
a():Begin...
a():Call b()...
b():Begin...
b():Call c()...
c():Begin...
c():Call d()...
d():Begin...
d():Jump now!a():Jumped back here with code 6
a():End...
main():a() returned...
main():End...
3.6 资源获取及控制
在shell中通过ulimit命令可以获取资源的相关信息
相关函数
- getrlimit(2) 获取resource资源并将其保存在rlimits中
/* Put the soft and hard limits for RESOURCE in *RLIMITS.
Returns 0 if successful, -1 if not (and sets errno). */
#ifndef __USE_FILE_OFFSET64
extern int getrlimit (__rlimit_resource_t __resource,
struct rlimit *__rlimits) __THROW;
- setrlimit(2) 将resource资源设置成rlimits中的内容
/* Set the soft and hard limits for RESOURCE to *RLIMITS.
Only the super-user can increase hard limits.
Return 0 if successful, -1 if not (and sets errno). */
#ifndef __USE_FILE_OFFSET64
extern int setrlimit (__rlimit_resource_t __resource,
const struct rlimit *__rlimits) __THROW;
- rlimit结构体
软限制可以升高可以降低,但是不能超过硬限制,硬限制就如同一个标杆
普通用户可以降低自己的硬限制,但是不能升高
root用户对于自己的硬限制可以升高可以降低
struct rlimit {
rlim_t rlim_cur; /* Soft limit 软限制*/
rlim_t rlim_max; /* Hard limit (ceiling for rlim_cur) 硬限制*/
};
4 shell中ls命令的实现
实现shell中ls的-l -a -i -n选项
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
#include <time.h>
#include <unistd.h>
#include <glob.h>
#include <pwd.h>
#include <grp.h>
#define SIZE 5
#define DIRSIZE 1024
#define BUFSIZE 64
/**
* 实现ls的-l, -i, -a, -n功能
* 0, 1, 2, 3, {4表示是否有自己加入的目录}
*/
// 返追加文件类型,权限,等信息
static void appendInfo(struct stat * fStat, char *fname, int lnFlag) {
char buf[BUFSIZE];
struct passwd *pw;
struct group *gr;
struct tm *time;
char *map[12] = {"Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"};
//1 文件类型
if (S_ISREG(fStat->st_mode)) {
strncat(fname, "-", DIRSIZE);
} else if (S_ISDIR(fStat->st_mode)) {
strncat(fname, "d", DIRSIZE);
} else if (S_ISCHR(fStat->st_mode)) {
strncat(fname, "c", DIRSIZE);
} else if (S_ISBLK(fStat->st_mode)) {
strncat(fname, "b", DIRSIZE);
} else if (S_ISFIFO(fStat->st_mode)) {
strncat(fname, "p", DIRSIZE);
} else if (S_ISLNK(fStat->st_mode)) {
strncat(fname, "l", DIRSIZE);
} else if (S_ISSOCK(fStat->st_mode)) {
strncat(fname, "s", DIRSIZE);
}
//2 权限信息
//a user
if ((fStat->st_mode & S_IRUSR) == S_IRUSR) {
strncat(fname, "r", DIRSIZE);
} else {
strncat(fname, "-", DIRSIZE);
}
if ((fStat->st_mode & S_IWUSR) == S_IWUSR) {
strncat(fname, "w", DIRSIZE);
} else {
strncat(fname, "-", DIRSIZE);
}
if ((fStat->st_mode & S_IXUSR) == S_IXUSR) {
strncat(fname, "x", DIRSIZE);
} else {
strncat(fname, "-", DIRSIZE);
}
//b group
if ((fStat->st_mode & S_IRGRP) == S_IRGRP) {
strncat(fname, "r", DIRSIZE);
} else {
strncat(fname, "-", DIRSIZE);
}
if ((fStat->st_mode & S_IWGRP) == S_IWGRP) {
strncat(fname, "w", DIRSIZE);
} else {
strncat(fname, "-", DIRSIZE);
}
if ((fStat->st_mode & S_IXGRP) == S_IXGRP) {
strncat(fname, "x", DIRSIZE);
} else {
strncat(fname, "-", DIRSIZE);
}
//c others
if ((fStat->st_mode & S_IROTH) == S_IROTH) {
strncat(fname, "r", DIRSIZE);
} else {
strncat(fname, "-", DIRSIZE);
}
if ((fStat->st_mode & S_IWOTH) == S_IWOTH) {
strncat(fname, "w", DIRSIZE);
} else {
strncat(fname, "-", DIRSIZE);
}
if ((fStat->st_mode & S_IXOTH) == S_IXOTH) {
strncat(fname, "x", DIRSIZE);
} else {
strncat(fname, "-", DIRSIZE);
}
strncat(fname, " ", DIRSIZE);
//3 硬链接数
snprintf(buf, BUFSIZE, "%ld ", fStat->st_nlink);
strncat(fname, buf, DIRSIZE);
//4 用户和组名
if (lnFlag) {
snprintf(buf, BUFSIZE, "%u %u ", fStat->st_uid, fStat->st_gid);
strncat(fname, buf, DIRSIZE);
} else {
pw = getpwuid(fStat->st_uid);
gr = getgrgid(fStat->st_gid);
snprintf(buf, BUFSIZE, "%s %s ", pw->pw_name, gr->gr_name);
strncat(fname, buf, DIRSIZE);
}
//5 文件大小
snprintf(buf, BUFSIZE, "%8ld ", fStat->st_size);
strncat(fname, buf, DIRSIZE);
//6 修改日期
time = localtime(&(fStat->st_mtim.tv_sec));
snprintf(buf, BUFSIZE, "%-4s%3d %d:%-3d", map[time->tm_mon], time->tm_mday,time->tm_hour, time->tm_min);
strncat(fname, buf, DIRSIZE);
}
static int errFunc(const char *errPath, int errno) {
fprintf(stderr, "ERROR MSG:%s\n", strerror(errno));
return 0;
}
/**
* 该函数用来解决只有-a或-i的打印
*/
static void print_a_i(glob_t * globres, int flag) {
int i, cnt = 0;
struct stat st;
char fname[DIRSIZE];
char tmp[BUFSIZE];
for (i = 0; globres->gl_pathv[i] != NULL; i++) {
if (lstat(globres->gl_pathv[i], &st) < 0) {
perror("lstat():");
}
if (flag) {
snprintf(tmp, BUFSIZE, "%ld ", st.st_ino);
strncpy(fname, tmp, DIRSIZE);
strncat(fname, strrchr(globres->gl_pathv[i], '/') + 1, DIRSIZE);
} else {
strncpy(fname, strrchr(globres->gl_pathv[i], '/') + 1, DIRSIZE);
strncat(fname, " ", DIRSIZE);
}
printf("%-24s", fname);
cnt++;
if (cnt == 8) {
printf("\n");
cnt = 0;
}
}
}
/**
* 该函数用来解决有l和n的输出
*/
static void print_l_n(glob_t * globres, int iFlag, int lnFlag) {
int i;
struct stat st;
char fname[DIRSIZE];
char tmp[BUFSIZE];
for (i = 0; globres->gl_pathv[i] != NULL; i++) {
if (lstat(globres->gl_pathv[i], &st) < 0) {
perror("lstat():");
}
if (iFlag) {
snprintf(tmp, BUFSIZE, "%-8ld", st.st_ino);
strncpy(fname, tmp, DIRSIZE);
} else {
strncpy(fname, "", DIRSIZE);
}
appendInfo(&st, fname, lnFlag);
strncat(fname, strrchr(globres->gl_pathv[i], '/') + 1, DIRSIZE);
strncat(fname, "\n", DIRSIZE);
printf("%s", fname);
}
}
int main(int argc, char **argv) {
int map[SIZE] = {0};
int c, i;
char *dir[DIRSIZE] = {NULL};//存储命令行中的目录
char pwd[DIRSIZE];
char path[DIRSIZE];
int idx = 0;
glob_t globres;
while (1) {
c = getopt(argc, argv, "-lian");
if (c < 0) break;
switch (c){
case 1:
map[4] = 1;
dir[idx++] = argv[optind - 1];
break;
case 'l':
if (map[3]) map[3] = 0;
map[0] = 1;
break;
case 'i':
map[1] = 1;
break;
case 'a':
map[2] = 1;
break;
case 'n':
if (map[0]) map[0] = 0;
map[3] = 1;
break;
default:
break;
}
}
if (map[4] == 0) { //没有输入的路径则将当前工作路径放入
if (getcwd(pwd, DIRSIZE) == NULL) {
fprintf(stderr, "Present working directory can not find...\n");
};
dir[idx++] = pwd;
}
for (i = 0; dir[i] != NULL; i++) {
//1 获取所有非隐藏文件的信息
strncpy(path, dir[i], DIRSIZE);
strncat(path, "/*",DIRSIZE);
if (glob(path, 0, errFunc, &globres) < 0) {
fprintf(stderr, "glob %s error...", path);
}
if (map[2]) { //2 获取所有隐藏文件的信息
strncpy(path, dir[i], DIRSIZE);
strncat(path, "/.*",DIRSIZE);
if (glob(path, GLOB_APPEND, errFunc, &globres) < 0) {
fprintf(stderr, "glob %s error...", path);
}
}
//3 打印出来所有文件的信息
printf("%s:\n", dir[i]);
//3.1 如果没有l与n
if (map[0] == 0 && map[3] == 0) {
print_a_i(&globres, map[1]);
printf("\n");
} else { //3.2 l和n有一个存在
if (map[0]) {
if (map[1]) print_l_n(&globres, 1, 0);
else print_l_n(&globres, 0, 0);
} else {
if (map[1]) print_l_n(&globres, 1, 1);
else print_l_n(&globres, 0, 1);
}
if (dir[i + 1] != NULL) printf("\n");
}
}
globfree(&globres);
exit(0);
}