JOE'S BLOG

好记性不如烂键盘

0%

前言

守护进程(daemon)是在系统引导装入时启动,仅在系统关闭时才终止。守护进程是在后台运行的。

守护进程特征

在命令行下输入 ps -axj(BSD系统) 查看系统中各个进程的状态。

Read more »

文件I/O

3.2 文件描述符

  对于内核而言,所有打开的文件都通过文件描述符引用,文件描述符是一个非负整数。
  UNIX系统shell把文件描述符0与进程的标准输入关联,1与标准输出关联,2与标准错误关联
通常 把0,1,2 这三个幻数替换成常量 STDIN_FILENO、STDOUT_FILENO、STDERR_FILENO

  • 0 标准输入
  • 1 标准输出
  • 2 标准错误输出

3.3 函数open和openat

1
2
3
4
5
6
#include <fcntl.h>
int open(const char *path, int oflag, ... /* mode_t mode */);
int openat(int fd, const *path, int oflag, ... /* mode_t mode */);
// 最后一个参数 ... ISO C用这种方法表明余下的参数的数量及其类型是可变的。
//open 函数 只有当创建新文件时才使用最后这个参数
//函数的返回值:若成功,返回文件描述符,若出错,返回-1

path 参数是要打开或创建文件的名字。oflag参数 用来说明该函数的多个选项。
有以下一个或多个常亮进行 “或”运算构成oflag参数 (这些常量定义在<fcntl.h>)
下面这些常量是必须的

  • O_RDONLY Open for reading only.
  • O_WRONLY Open for writing only.
  • O_RDWR Open for reading and writing.
  • O_EXEC Open for execute only.
  • O_SEARCH Open for search only(applies to directories).

这5个常量中必须指定一个且只能指定一个。还有一些常量是可选的(慢慢补充)

  • O_APPEND 每次写都追加到文件末尾
  • O_CLOEXEC
  • O_CREAT 若此文件不存在则创建它,用mode指定该文件的访问权限位
  • O_TRUNC 如果此文件存在,并且为只写或读-写成功打开,则将其长度截断为0

3.6 函数lseek

1
2
3
#include <unistd.h>
off_t lseek(int fd, off_t offset, int whence);
//返回值:若成功,返回新的文件的偏移量,若出错,返回-1

whence 参数

  • whence是 SEEK_SET,将文件偏移量设置为据文件开始处offset个字节
  • whence是 SEEK_CUR,将文件偏移量设置为当前值加上offset,offset可为正或负
  • whence是 SEEK_END,将文件偏移量设置为文件长度加上offset,offset可为正或负
    在比较lseek的返回值时要谨慎,不要测试它是否小于0,而应该测试是否等于-1
1
2
3
4
5
6
7
8
9
10
#include "apue.h"
int main(void)
{
if (lseek(STDIN_FILENO, 0, SEEK_CUR) == -1)
printf("cannot seek\n");
else
printf("seek ok\n");

exit(0);
}

3.7 函数read

1
2
3
4
//read 函数从打开的文件中读取数据
#include <unistd.h>
ssize_t read(int fd,void *buf,size_t nbytes);
//返回值:读到的字节数,如果到达文件末尾返回0,若出错返回-1

3.8 函数write

调用write函数向打开的文件写数据

1
2
3
#include <unistd.h>
ssize_t write(int fd, const void *buf, size_t nbytes);
//成功 返回已写入的字节数,失败返回-1

write 出错的一个常见原因是磁盘已写满 或者超过了一个给定进程的文件长度限制
普通文件 写操作从当前文件偏移量开始。打开文件时指定了O_APPEND选项(O_APPEND每次写都追加到文件末尾),
每次写操作将文件偏移量设置在文件的当前结尾处,在一次写成功后,该文件偏移量增加实际写的字节数

3.9 I/O的效率

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
//只使用read 和 write函数 复制文件
#include "apue.h"
#define BUFFSIZE 4096
//设置为4096的原因 是系统CPU的时间的最小值差不多是BUFFSIZE为4096或之后的值,
//继续增加缓冲区长度对这个时间几乎没有影响
int main(void)
{
int n;
char buf[BUFFISZE];
while ((n = read(STDIN_FILENO, buf, BUFFSIZE)) > 0)
if (write(STDOUT_FILENO, buf, n) != n)
err_sys("write error");

if (n < 0 )
err_sys("read error");
exit(0);
}

3.11 原子操作

  • 追加文件时如果使用lseek设置文件偏移量到末尾 然后使用write函数写入文件,会出现问题.
  • 有两个进程的话 可能会发生覆盖数据的情况

UNIX系统为这样的操作提供了一种原子操作,即在打开文件时时设置O_APPEND标志,使得进程每次write时都将文件偏移量设置到文件末尾处,也就不需要lseek函数。

  1. 函数pread和pwrite
    原子定位读和原子定位写
    1
    2
    3
    #include <unistd.h>
    ssize_t pread(int fd, void *buf, size_t nbytes, off_t offset);
    ssize_t pwrite(int fd, const void *buf, size_t nbytes, off_t offset);
  • 参数:
    • fd: 文件描述符
    • buf: 文件缓冲区
    • nbytes: 预计读或写的数据字节数
    • offset: 文件偏移
  • 返回:
    • 成功: pread返回读到的字节数,write返回已写的字节数
    • 失败: 返回-1

调用pread/pwrite相当于先调用lseek再调用read/write,但又与这种调用有所区别

  • 调用pread/pwrite时无法中断其定位读/写操作
  • 不更新当前文件偏移量

3.12 dup和dup2函数(未更新完)

用来复制文件描述符

1
2
3
#include <unistd.h>
int dup(int fd);
int dup2(int fd, int fd2);
  • 参数:
    • fd: 描述符的值
    • fd2: 新的描述符的值。
  • 返回:
    • 成功: 返回新的文件描述符
    • 失败: 返回-1

如果 fd == fd2 则返回fd2的文件描述符,不关闭它。否则fd2的FD_CLOEXEC文件描述符标志被清除,这样fd2在进程调用exec时是打开状态
返回的新的文件描述符与参数fd共享同一个文件表项

3.13 sync,fsync,fdatasync 函数

函数原型

1
2
3
4
5
#include <unistd.h>
int fsync(int fd);
int fdatasync(int fd);

void sync(void);
  • sync: 将所有修改过的块缓存写入到队列,然后返回
  • fsync: 只针对由文件描述符fd指定的一个文件起作用,等待磁盘写操作完成后才结束(waits for the disk writes to complete before returning)。可用于数据库这样的应用程序
  • fdatasync: 除了数据部分,还会同步更新文件属性

3.14 fcntl函数

1
2
3
4
// 改变已打开的文件属性
#include <fcntl.h>
int fcntl(int fd, int cmd, ...);

文件和目录

本章主要讲解了文件系统的其他特征和文件的性质。

4.2 函数stat,fstat,fstatat和lstat

1
2
3
4
5
#include <sys/stat.h>
int stat(const char * restrict pathname,struct stat *restrict buf);
int fstat(int fd,struct stat *buf);
int lstat(const char *restrict pathname,struct stat *restrict buf);
int fstatat(int fd,const char *restrict pathname,struct stat *restrict buf,int flag);

成功返回0,失败返回-1

  • stat 函数返回文件有关的信息结构
  • fstat 函数获得已在描述符fd上打开文件的有关信息
  • lstat 函数类似于stat,当命名的文件是一个符号链接时,lstat返回该符号链接的有关信息,而不是该符号链接引用的文件的信息
  • fstatat 函数为一个相对于当前打开目录(由fd参数指定)的路径名返回文件统计信息。flag参数控制着是否跟随着一个符号链接。

buf参数是一个指针,指向一个我们必须提供的结构 在mac系统上该结构的定义如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30

struct stat {
dev_t st_dev; /* [XSI] ID of device containing file */
ino_t st_ino; /* [XSI] File serial number */
mode_t st_mode; /* [XSI] Mode of file (see below) */
nlink_t st_nlink; /* [XSI] Number of hard links */
uid_t st_uid; /* [XSI] User ID of the file */
gid_t st_gid; /* [XSI] Group ID of the file */
dev_t st_rdev; /* [XSI] Device ID */
#if !defined(_POSIX_C_SOURCE) || defined(_DARWIN_C_SOURCE)
struct timespec st_atimespec; /* time of last access */
struct timespec st_mtimespec; /* time of last data modification */
struct timespec st_ctimespec; /* time of last status change */
#else
time_t st_atime; /* [XSI] Time of last access */
long st_atimensec; /* nsec of last access */
time_t st_mtime; /* [XSI] Last data modification time */
long st_mtimensec; /* last data modification nsec */
time_t st_ctime; /* [XSI] Time of last status change */
long st_ctimensec; /* nsec of last status change */
#endif
off_t st_size; /* [XSI] file size, in bytes */
blkcnt_t st_blocks; /* [XSI] blocks allocated for file */
blksize_t st_blksize; /* [XSI] optimal blocksize for I/O */
__uint32_t st_flags; /* user defined flags for file */
__uint32_t st_gen; /* file generation number */
__int32_t st_lspare; /* RESERVED: DO NOT USE! */
__int64_t st_qspare[2]; /* RESERVED: DO NOT USE! */
};

文件类型

文件类型
S_ISREG() 普通文件
S_ISDIR() 目录文件
S_ISCHR() 字符特殊文件
S_ISBLK() 块特殊文件
S_ISFIFO() 普通或FIFO
S_ISLNK() 符号链接
S_ISSOCK() 套接字

取命令行参数,然后根据每个命令行参数打印文件类型

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
/*
对每个命令行参数打印文件类型
*/
#include "apue.h"
int main(int argc, char *argv[])
{
int i;
struct stat buf;
char *ptr;
for(i = 1; i < argc; i++) {
printf("%s: ",argv[i]);
if (lstat(argv[i],&buf) < 0) {
err_sys("lstat error");
continue;
}
printf("%hu\n",buf.st_mode);
printf("%d\n",S_ISREG(buf.st_mode));
if (S_ISREG(buf.st_mode))
ptr = "regular";
else if (S_ISDIR(buf.st_mode))
ptr = "directory";
else if (S_ISCHR(buf.st_mode))
ptr = "character special";
else if (S_ISBLK(buf.st_mode))
ptr = "block special";
else if (S_ISFIFO(buf.st_mode))
ptr = "fifo";
else if (S_ISLNK(buf.st_mode))
ptr = "symbolic link";
else if (S_ISSOCK(buf.st_mode))
ptr = "socket";
else
ptr = "unknown mode";
printf("%s\n",ptr);
}
exit(0);
}
//这些宏的定义如下
/*
#define S_ISBLK(m) (((m) & S_IFMT) == S_IFBLK)
#define S_ISCHR(m) (((m) & S_IFMT) == S_IFCHR)
#define S_ISDIR(m) (((m) & S_IFMT) == S_IFDIR)
#define S_ISFIFO(m) (((m) & S_IFMT) == S_IFIFO)
#define S_ISREG(m) (((m) & S_IFMT) == S_IFREG)
#define S_ISLNK(m) (((m) & S_IFMT) == S_IFLNK)
#define S_ISSOCK(m) (((m) & S_IFMT) == S_IFSOCK)
#if !defined(_POSIX_C_SOURCE) || defined(_DARWIN_C_SOURCE)
#define S_ISWHT(m) (((m) & S_IFMT) == S_IFWHT)
#endif
*/

4.5 文件访问权限

除了文件外,目录字符特殊文件都有访问权限
每个文件有9个访问权限位,分为如下3类
在 <sys/stat.h>中定义

st_mode屏蔽 含义
S_IRUSR 用户读
S_IWUSR 用户写
S_IXUSR 用户执行
st_mode屏蔽 含义
S_IRGRP 组读
S_IWGRP 组写
S_IXGRP 组执行
st_mode屏蔽 含义
S_IROTH 其他读
S_IWOTH 其他写
S_IXOTH 其他执行
  • 当用名字打开一任意类型的文件时,对改名字包含的每一个目录,包括它可能隐含的当前工作目录都应具有执行权限
    • 对目录的读权限,可以列出目录的文件名列表
  • 对于一个文件的读权限决定了我们是否能够打开现有文件进行读操作。这与open函数的O_RDONLY和O_RDWR标志决定
  • 对于一个文件的写权限决定了我们是否能够打开现有文件进行写操作。这与open函数的O_WRONLY和O_RDWR标志决定
  • 为了在open函数中对一个文件指定O_TRUNC标志,必须对该文件具有写权限
  • 在一个目录中创建一个新文件,需对该目录有写权限和执行权限
  • 为了删除一个现有的文件,必须对包含该文件的目录具有写和执行权限,对该文件本身不需要有读、写权限

4.7 函数access和faccessat

当用open函数打开文件时,内核以进程的有效用户id和有效组id为基础执行其访问文件权限测试。但有时,进程希望按照其实际用户id和实际组id来测试访问能力。access函数和faccessat函数式按照实际id进行测试

1
2
3
4
#include <unistd.h>
int access(const char* pathname,int mode);
int faccessat(int fd,const char* pathaname,int mode,int flag);
//成功返回0,出错返回-1

如果测试文件存在,mode就为F_OK,否则mode为如下图中所列常量的按位或。

mode 说明
R_OK 测试读权限
W_OK 测试写权限
X_Ok 测试执行权限
代码示例
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
#include "apue.h"
#include <fcntl.h>
int main(int argc, char *argv[])
{
if (argc != 2)
err_quit("参数不够");

if (access(argv[1],F_OK) < 0)
err_ret("权限错误: %s",argv[1]);
else
printf("读取权限 OK\n");

if (open(argv[1],O_RDONLY) < 0)
err_ret("打开文件错误: %s",argv[1]);
else
printf("打开文件 OK\n");

exit(0);
}

4.8 函数umask

umask为进程设置文件模式创建屏蔽字,并返回之前的值(没有出错返回)

1
2
#include <sys/stat.h>
mode_t umask(mode_t cmask);

cmask 参数有9个访问权限位若干个按位”或”构成
常见的几种umask值

  • 002 阻止其他用户写入你的文件
  • 022 阻止同组成员和其他用户写入你的文件
  • 027 阻止同组成员写你的文件以及其他用户读、写、执行你的文件

4.12 文件长度

stat 结构成员st_size 表示以字节为单位的文件长度,改字段只对普通文件,目录文件,链接文件有作用

  • 普通文件,其文件长度可以为0,从文件开始到end-of-file。
  • 目录文件,是一个数的整数倍(例如16或512)
  • 链接文件,文件名中的实际字节数

4.13 文件截断

1
2
3
4
5
#include <unistd.h>
int truncate(const char *pathname,off_t length);
int ftruncate(int fd, off_t length);
//成功返回0,失败返回-1

如果文件长度大于length,则length后的数据不能访问,如果小于,文件长度增加。增加的这段填充0(可能在文件中创建了一个空洞)

4.14 文件系统

任何一个文件可以有多个目录项指向其i节点。创建一个现有文件的链接的方法是使用link或linkat函数

1
2
3
4
5
#include <unistd.h>
int link(const char* existingpath, const char* newpath);
int linkat(int efd, const char* existingpath, int nfd, const char* newpath, int flag);
//成功返回0,出错返回-1

  • 这俩函数创建一个新目录项newpath,引用现有文件existingpath。如果newpath已经存在则出错。只创建newpath中最后一个分量。
  • linkat函数,现有文件和新的路径名都是通过efd和existingpath、nfd和newpath指定。nfd和efd是文件描述符,如果目录是相对目录就根据文件描述符进行计算,如果是绝对路径该参数将会忽略。
  • 当现有文件是符号链接时,由flag参数来控制linkat函数是创建现有符号链接的链接还是创建执行现有符号链接指向的文件的链接。如果在flag参数中设置了AT_SYMLINK_FOLLOW标志,就创建指向符号链接目标的链接。如果这个标志被清除则创建一个指向符号链接本身的链接。

删除一个目录项,可以调用unlink函数

1
2
3
4
#incude <unistd.h>
int unlink(const char* pathname);
int unlinkat(int fd, const char* pathname, int flag);
//成功返回0,出错返回-1

删除目录项,并将由pathname所引用文件链接计数减1。只有当链接计数达到0时,该文件才被删除。
为了解除对文件的链接,必须对包含该目录项的目录具有写和执行权限。如果该目录设置了粘着位,则对该目录必须具有写权限,并且要具备下面三个条件之一:

  • 拥有该文件
  • 拥有该目录
  • 具有超级用户权限

unlink通常用于确保即使程序崩溃,它所创建的临时文件也不会遗留下来。进程用open和creat创建一个文件,然后立即调用unlink,因为该文件仍然是打开的,所以不会将其内容删除。只有当进程关闭该文件或终止时,该文件的内容才被删除。

remove函数

1
2
3
#include <stdio.h>
int remove(const char* pathname);
//成功返回0,失败返回-1

对于文件remove与unlink的功能类似,对于目录与rmdir相同

4.16 函数rename与renameat

文件或目录可以使用rename和renameat重命名

1
2
3
4
#include <stdio.h>
int rename(const char* oldname, const char* newname);
int renameat(int oldfd, const char* oldname, int newfd, const char* newname);
//成功返回0,失败返回-1

oldname参数是 目录、文件还是符号链接有以下几点说明

  1. 如果oldname指的是一个文件而不是目录,那么为该文件或符号链接重命名。在这种情况下如果newname已经存在,则它不能引用一个目录。如果newname已经存在并且不是一个目录,则先将该目录项删除然后将oldname重命名为newname。对包含oldname的目录以及newname的目录,调用进程必须有写权限,因为要更改该目录
  2. 如果oldname指的是一个目录,那么为该目录重命名。如果newname已经存在,则它必须引用一个目录,而且该目录是空目录(空目录指只包含.和..项)。如果newname存在,则先将其删除,然后将oldname重命名为newname。另外,当为一个目录重命名时,newname不能包含oldname作为其路径前缀。例如不能将/usr/foo重命名为/usr/foo/test,因为旧名字是新名字的路径前缀,因而不能讲其删除。
  3. 如果oldname和newname引用的是符号链接,则处理的是符号链接本身,而不是符号链接引用的文件
  4. 不能对.和..重命名
  5. 如果oldname和newname引用同一个文件,则不做任何改动 返回

4.17 符号链接(软链接)

符号链接是对一个文件的间接指针,引用符号链接是避免硬链接的一些限制

  • 硬链接通常要求链接和文件在同一个文件系统中
  • 只有超级用户才能创建指向目录的硬链接(在底层文件系统的支持下)

符号链接一般用于将一个文件或整个目录结构移到系统的另一个位置。符号链接有着自己的inode号和用户数据块

4.19 文件的时间

对每个文件通常维护3个时间段

字段 说明 例子 ls选项
st_atim 文件数据的最后访问时间 read -u
st_mtim 文件数据的最后修改时间 write 默认
st_ctim i节点状态的最后更改时间 chmod,chown -c

线程

线程的一些基础知识,包括线程的创建、线程终止

线程的概念

典型的UNIX进程可以看成只有一个控制线程;一个进程在某一时刻只能做一件事情。有了多个控制线程后,在程序设计时就可以把进程设计成在某一时刻做不止一件事,每个线程处理各自独立的任务。这种方法有很多好处

  1. 简化处理异步事件的代码
  2. 分解复杂的问题提高整个程序的吞吐量
  3. 使用多线程改善响应时间
Read more »

本文是李笑来正在预售的《七年就是一辈子》中的内容,略经 36 氪编辑。作者李笑来的微信公众号为 “学习学习再学习(xiaolai-xuexi)”。

书大抵上分为两种:虚构类(Fiction),非虚构类(Non-Fiction)。读非虚构类的书籍,是为了获取新知识,并应用到生活中,让自己获得进步;就我个人而言,我更喜欢看非虚构类的书籍,从中获得的愉悦,感觉更大,因为那种愉悦通常有更为坚实的支撑 —— 进步。而虚构类的书籍,在大学毕业之后已经很少读了,可能是因为我自己在那里很难体会到太多的乐趣,更可能的另外一个原因是我已经彻底把获取那种愉悦的途径转移到了影视作品上。

Read more »

环境准备

各个环境下有所区别,我使用的是mac系统,首先说的是mac系统下的安装

mac系统 自带的有php,这里并不用自带的。使用mamp,php的目录在如下的目录下

1
/Applications/mamp/bin/php
Read more »

布局

CSS 布局

  • CSS知识体系的重中之重
  • 早期以table为主(简单)
  • 后来以技巧性布局为主(难)
  • 现在有flexbox/grid(偏简单)
  • 响应式布局是必备知识

常用布局方法

  • table 表格布局
  • float 浮动+margin
  • inline-block 布局
  • flexbox 布局
Read more »

优秀软件收集目录

k8s
traefik

消息队列

简单的可以使用redis
可靠的可以使用
rabbitmq
kafka

私有存储

mimo
Cloudreve: go实现的网盘

部署/发布

gitlab
jenkins
docker

分布式

分布式redis
codis: https://github.com/CodisLabs/codis/blob/release3.2/doc/FAQ_zh.md
分布式数据库
tidb: https://github.com/pingcap/tidb
分布式id生成
snowflake: snowflake的go实现 用于分布式id生成器 https://github.com/bwmarrin/snowflake

压测

ab
wrk: 类似ab压测,可测试api web的性能: https://github.com/wg/wrk
sysbench: 数据库,磁盘,内存压测
redis-benchmark: redis的压测工具

工具

uuid 生成 https://github.com/satori/go.uuid
excel 操作 https://gitee.com/xurime/excelize
可视化展示docker image: https://github.com/wagoodman/dive
spiped: ssl代理,可以用在redis之间安全的传递数据

引言

本章主要介绍了UNIX系统的进程控制

  • 创建新进程、执行程序和进程终止
  • 进程属性的各种ID,他们如何受到进程控制原语的影响
  • 解释器文件和system函数
  • 进程会计机制
Read more »