LinuxC—文件系统学习笔记

news2025/1/14 18:20:32

文件系统

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

}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/145503.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Jetson nano 入手系列之5—远程可视化访问:jupyter lab与VNC连接

Jetson nano 入手系列之5—远程可视化访问&#xff1a;jupyter lab与VNC1. jupyter lab1.1 安装jupyter lab1.2 配置jupyter_lab1.3 打开jupyter lab1.3.1 ip地址方式打开1.3.2 cmd中ssh方式打开2. vino与VNC Viewer2.1 vino的安装2.2 Desktop Sharing配置与设置2.3 启动vino s…

ICG-DBCO;吲哚菁绿-二苯基环辛炔,荧光染料标记DBCO

中文名&#xff1a;吲哚菁绿-二苯基环辛炔 英文名&#xff1a;ICG-DBCO&#xff0c;ICG-Dibenzocyclooctyne 分子式: C63H64N4O5S 分子量: 989.27 g/mol 外观&#xff1a;绿色粉末 激发发射波长:785/821nm 结构式&#xff1a; ​ 溶解度&#xff1a;有机溶剂/水 储藏方法…

使用无人机 LiDAR 的重叠树冠的新型植被点云密度树分割模型

Paper题目&#xff1a;A Novel Vegetation Point Cloud Density Tree-Segmentation Model for Overlapping Crowns Using UAV LiDAR Abstract 由于常用的冠层高度模型(CHM)的局限性&#xff0c;在具有高密度和重叠树冠的森林生态系统中检测和分割单个树木经常会导致偏差。针对…

Flink 第3章 反压策略

概述Flink 中文网站的讲解https://flink-learning.org.cn/article/detail/138316d1556f8f9d34e517d04d670626涉及内容&#xff1a;网络流控的概念与背景TCP的流控机制Flink TCP-based 反压机制 1.5之前Flink Credit-based 反压机制 1.5及以后总结与思考网络流控的概念与背景为什…

AtCoder Beginner Contest 283 E - Don‘t Isolate Elements

E - Dont Isolate Elements (atcoder.jp)题意&#xff1a;题意&#xff1a;定义孤独的数为&#xff0c;该数上下左右的数都和它相反给定一个01矩阵&#xff0c;每次操作可以把某一行的数取反&#xff0c;问你把该矩阵变成没有孤独的数的最少操作次数是多少思路&#xff1a;一开…

AI降噪的N种数据扩增方法

数据和特征决定了机器学习的上限&#xff0c;而模型和算法只是逼近这个上限而已 基于统计信号处理的传统噪声抑制方法是通过检测持续的背景声&#xff0c;来估计背景噪声&#xff0c;然后通过估计到的背景噪声计算增益因子对带噪语音进行抑制。但这种方式针对规律的稳态噪声比较…

【算法笔记】最近公共祖先(LCA)算法详解

0. 前言 最近公共祖先简称 LCA&#xff08;Lowest Common Ancestor&#xff09;。两个节点的最近公共祖先&#xff0c;就是这两个点的公共祖先里面&#xff0c;离根最远的那个。 这种算法应用很广泛&#xff0c;可以很容易解决树上最短路等问题。 为了方便&#xff0c;我们记…

企业内训方案|领导力与执行力/TTT内训师/管理者情商修炼

企业内训方案|领导力与执行力/TTT内训师/管理者情商修炼 》》领导力与执行力 从精兵到强将 高绩效团队协作与跨部门沟通 核心人才的管理与激励 卓越管理者的胜任力提升 MTP中层管理技能提升训练 打造高绩效团队 高效沟通技巧 高绩效团队管理&#xff08;中高层/中基层&#xf…

CRM帮助企业实现销售自动化

随着互联网技术的发展&#xff0c;各家企业都善用互联网优势发布各种信息&#xff0c;导致潜在客户被各种推销信息所淹没&#xff0c;销售周期延长&#xff0c;企业可以借助CRM有效规范销售流程&#xff0c;帮助企业实现销售自动化。 前言 各行各业的业务流程中似乎都少不了销…

OSPF综合实验(1.5)

目标&#xff1a; 1、首先进行基于172.16.0.0/16的ip地址规划 首先题中有5个区域和一个RIP共需要5个网段 可以借3位划分为8个网段 172.16.0.0/19 area 0 然后将172.16.0.0/19再借6位分为172.16.0.0/25---172.16.31.128 25作为其中前一个骨干ip网段 172.16.0.0/25在用于只…

TCP滑动窗口机制(附图例)

文章目录前言一、滑动窗口的引出二、流量控制2.1 16位窗口大小2.2 发送缓冲区2.3 逐步解析滑动窗口运作三、快重传机制四、拥塞控制&#xff08;仅供参考&#xff09;五、延迟应答与捎带应答&#xff08;略&#xff09;总结前言 博主个人社区&#xff1a;开发与算法学习社区 博…

测开-刷笔试题时的知识点

圈复杂度&#xff08;暂缓&#xff09;复杂度越大&#xff0c;程序越复杂计算公式&#xff1a;V(G) E - N 2E代表控制流边的数量&#xff0c;n代表节点数量V (G) P 1p为判定节点数几种常见的控制流图&#xff1a;Linux文件权限具有四种访问权限&#xff1a;r&#xff08;可…

进程信号理解3

进程信号理解3 1.什么叫做信号递达 实际执行信号的处理动作叫做信号递达&#xff0c;比如默认&#xff0c;忽略&#xff0c;自定义动作 2.什么叫做信号未决&#xff1f; 信号产生到信号递达的状态叫做信号未决 3.进程被阻塞和进程被忽略有什么区别&#xff1f; 进程被阻塞属…

iPhone更换字体教程,无需越狱,支持所有苹果设备!

上周开始&#xff0c;技术大神zhuowei 发现了一个iOS系统更换字体的漏洞&#xff0c;经过不断修正&#xff0c;现在已经可利用上了&#xff01; 先来看看更换字体后的效果&#xff0c;更换之后&#xff0c;所有App上的字体都得到更改&#xff0c;下图是打开文章的效果 下图是聊…

excel查重技巧:如何用组合函数快速统计重复数据(上)

统计不重复数据的个数&#xff0c;相信不少小伙伴在工作中都遇到过这样的问题。通常的做法都是先把不重复的数据提取出来&#xff0c;再去统计个数。而提取不重复数据的方法之前也分享过&#xff0c;基本有三种方法&#xff1a;高级筛选、数据透视表和删除重复项。其实使用公式…

Ngnix 实现访问黑名单功能

前言 有时候在配置的时候我们会禁用到一些IP&#xff0c;使用nginx 禁用到ip但是需要重启nginx&#xff0c;这样当我们要是实现动态的这种就比较麻烦&#xff0c;当然你可以使用网关来实现相对于nginx实现的这种方式要好很多&#xff0c;但是今天咱们说到这里&#xff0c;那就…

数据可视化系列-05数据分析报告

文章目录数据可视化系列-05数据分析报告1、了解初识数据分析报告数据分析报告简介数据分析报告的作用报告的能力体现报告编写的原则报告种类2、掌握数据分析报告结构标题页目录前言正文结论与建议附录3、了解报告的描述规范报告注意事项报告表达的维度数据结论可用指标数据可视…

代码随想录算法训练营第3天| 203. 移除链表元素、206. 反转链表

代码随想录算法训练营第3天| 203. 移除链表元素、206. 反转链表 移除链表元素 力扣题目链接 删除链表中等于给定值 val 的所有节点。 这里以链表 1 4 2 4 来举例&#xff0c;移除元素4。 那么因为单链表的特殊性&#xff0c;只能指向下一个节点&#xff0c;刚刚删除的是链表…

RS485通信----基本原理+电路图

一、RS485 通信----简介 RS485 是美国电子工业协会&#xff08;Electronic Industries Association&#xff0c;EIA&#xff09;于1983年发布的串行通信接口标准&#xff0c;经通讯工业协会&#xff08;TIA&#xff09;修订后命名为 TIA/EIA-485-A。 RS485 是一种工业控制环境…

获取Java集合中泛型的Class对象

直接获取时获取不到的&#xff0c;类型被虚拟机擦除了 泛型的正常工作是依赖编译器在编译源码的时候&#xff0c;先进行类型检查&#xff0c;然后进行类型擦除并且在类型参数出现的地方插入强制转换的相关指令实现的。编译器在编译时擦除了所有类型相关的信息&#xff0c;所以…