前言

打开自己的blog一看,居然三个月没更新了...回想一下前几个月,开题 + 实验室杂活貌似也没占非常多的时间,还是自己太懈怠了吧,掉线城和文明6真的是时间刹手(

不过好消息是把15445的所有lab都锤完了,最近一个月应该没啥活干。立个flag,这个月更它个10篇blog,把15445的知识点、lab,以及6.S081想写的东西都写完。今天先做个复健,码一下刚做完的lab8,以及xv6的file system的学习笔记。

坏消息是leetcode一道没动,甚至主力啥语言啥框架还没定下来,开学找实习可能要炸了orz...

这个lab不算难,总代码量也就几十行,绝大多数时间拿来读文件系统相关的代码了(两三天吧),不过收获也挺大,理清楚xv6文件系统的逻辑后,lab一个晚上就搞定了。因此这篇blog我会写的简单一些,大头部分放在file system分析的blog中。

强烈推荐在做这个lab前把《xv6 book》中关于file system的章节全部看完,并详细分析相关的代码。我的这篇关于xv6的file system的学习笔记可能对你有帮助:

TODO:(在码了在码了,但愿今天能码完)

Lab链接:https://pdos.csail.mit.edu/6.828/2019/labs/fs.html

都2021年了还在做2019的lab,这真的好嘛(

Part1 Large Files

xv6选择的文件存储介质是磁盘,通过 struct dinode 来描述一个文件在磁盘上存储,并用 struct inode作为其对一个的struct dinode的拷贝,存储在内存中:

// kernel/fs.h
struct dinode {
short type; // File type
short major; // T_DEVICE only
short minor; // T_DEVICE only
short nlink; // Number of links to inode in file system
uint size; // Size of file (bytes)
uint addrs[NDIRECT+1]; // Data block addresses
}; // kernel/file.h
// in-memory copy of an inode
struct inode {
uint dev; // Device number
uint inum; // Inode number
int ref; // Reference count
struct sleeplock lock; // protects everything below here
int valid; // inode has been read from disk? short type; // copy of disk inode
short major;
short minor;
short nlink;
uint size;
uint addrs[NDIRECT + 1];
};

我们只关注其中的addrsaddrs记录着文件所在磁盘的盘块号,这个数组的容量为13,前12个地址是直接地址,即该文件的第 0 ~ 11 号盘块,第13个地址是一个一级索引,用于索引文件的第 12 ~ 12 + 256 号盘块,如下图所示:

这样,一个文件最多可以占用 268 个盘块。

bmap是xv6中非常重要的api,其返回文件偏移量(bn * BSIZE)所对应的的磁盘盘块号:

// kernel/fs.c
static uint
bmap(struct inode *ip, uint bn)
{
uint addr, *a;
struct buf *bp; if(bn < NDIRECT){ // offset in in NDIRECT range
if((addr = ip->addrs[bn]) == 0)
ip->addrs[bn] = addr = balloc(ip->dev);
return addr;
} bn -= NDIRECT; if(bn < NINDIRECT){
// Load indirect block. If indirect block is not exist, allocate it.
if((addr = ip->addrs[NDIRECT]) == 0)
ip->addrs[NDIRECT] = addr = balloc(ip->dev);
bp = bread(ip->dev, addr);
a = (uint*)bp->data;
if((addr = a[bn]) == 0){
a[bn] = addr = balloc(ip->dev);
log_write(bp);
}
brelse(bp);
return addr;
}

当xv6删除文件时,要将文件所对应的盘块一一释放。如果这个文件存在一级索引块,那么除了释放间接索引块的表项所对应的盘块之外,还要将这块一级索引块一并释放:

static void
itrunc(struct inode *ip)
{
int i, j;
struct buf *bp;
uint *a; for(i = 0; i < NDIRECT; i++){ // free direct block
if(ip->addrs[i]){
bfree(ip->dev, ip->addrs[i]);
ip->addrs[i] = 0;
}
} if(ip->addrs[NDIRECT]){
bp = bread(ip->dev, ip->addrs[NDIRECT]);
a = (uint*)bp->data;
for(j = 0; j < NINDIRECT; j++){ // free block indexed by indirect-block
if(a[j])
bfree(ip->dev, a[j]);
}
brelse(bp);
bfree(ip->dev, ip->addrs[NDIRECT]); // free indirect-block
ip->addrs[NDIRECT] = 0;
}
}

Part1要求我们为xv6的文件系统增添一个二级索引。一个二级索引占据一个盘块,共计有256个表项,每个表项都指向一个一级索引块。由于addr的容量仍然是13,因此需要牺牲一个一级索引项,一个文件的盘块索引项也变成了 11个直接索引 + 1个一级索引 + 1个二级索引,共计可以索引 11 + 256 + 256 *256 = 65803个盘块,由此实现了最大文件大小的扩展。一个二级索引块的例子如下:

我们首先要修改一下宏 NINDIRECT,将其值改为11,并修改struct dinodestruct inode中addr的定义部分,以及有关文件大小的宏:

// kernel/fs.h
#define NDIRECT 11
#define NINDIRECT (BSIZE / sizeof(uint))
#define NDINDIRECT NINDIRECT * NINDIRECT
#define MAXFILE (NDIRECT + NINDIRECT + NDINDIRECT)

还有一些宏(NBLOCKS等)需要修改,在相应的实验手册中已经指出,因此不再赘述了。

随后需要修改两个api:bmapitrunc。在bmap中添加计算二级索引的相关代码,以及对应的盘块的分配代码:

static uint
bmap(struct inode *ip, uint bn)
{
uint addr, *a;
struct buf *bp; // offset in direct-block range
if(bn < NDIRECT){
if((addr = ip->addrs[bn]) == 0)
ip->addrs[bn] = addr = balloc(ip->dev);
return addr;
}
bn -= NDIRECT; // offset in primary-index range
if(bn < NINDIRECT){
// Load indirect block, allocating if necessary.
if((addr = ip->addrs[NDIRECT]) == 0)
ip->addrs[NDIRECT] = addr = balloc(ip->dev); // allocate a block on disk
bp = bread(ip->dev, addr);
a = (uint*)bp->data;
if((addr = a[bn]) == 0){
a[bn] = addr = balloc(ip->dev);
log_write(bp);
}
brelse(bp);
return addr;
} bn -= NINDIRECT;
struct buf *dindbuf, *indbuf; // double-indirect block buffer, indirect block buffer
// offset in secondary-index range
if (bn < NDINDIRECT) {
// get the DIRECT index block. if it's not exist, allocate it.
if ((addr = ip->addrs[NDIRECT + 1]) == 0)
ip->addrs[NDIRECT + 1] = addr = balloc(ip->dev);
dindbuf = bread(ip->dev, addr); // map it into buffer
uint *dind = (uint *)dindbuf->data;
if(dind[bn / NINDIRECT] == 0) { // allocate a indirect block for double-indirect index and log it.
dind[bn / NINDIRECT] = balloc(ip->dev);
log_write(dindbuf);
}
indbuf = bread(ip->dev, dind[bn / NINDIRECT]);
uint *ind = (uint *)indbuf->data;
if (ind[bn % NINDIRECT] == 0) { // allocate file block if it's not exist.
ind[bn % NINDIRECT] = balloc(ip->dev);
log_write(indbuf);
}
brelse(dindbuf);
brelse(indbuf);
return ind[bn % NINDIRECT];
} // out of range
panic("bmap: out of range");
}

此外,还要修改itrunc这个方法,添加从二级索引中释放盘块的支持代码:

static void
itrunc(struct inode *ip)
{
int i, j;
struct buf *bp;
uint *a;
// release DIRECT block
for(i = 0; i < NDIRECT; i++){
if(ip->addrs[i]){
bfree(ip->dev, ip->addrs[i]);
ip->addrs[i] = 0;
}
}
// release primary-index block
if(ip->addrs[NDIRECT]){
bp = bread(ip->dev, ip->addrs[NDIRECT]);
a = (uint*)bp->data;
for(j = 0; j < NINDIRECT; j++){
if(a[j])
bfree(ip->dev, a[j]);
}
brelse(bp);
bfree(ip->dev, ip->addrs[NDIRECT]);
ip->addrs[NDIRECT] = 0;
}
// release secondary-index block
if (ip->addrs[NDIRECT + 1]) {
struct buf *dinddbuf, *indbuf;
uint *dind, *ind;
dinddbuf = bread(ip->dev, ip->addrs[NDIRECT + 1]);
dind = (uint *)dinddbuf->data;
for (int k = 0; k < NINDIRECT; k++) {
if (dind[k]) {
indbuf = bread(ip->dev, dind[k]);
ind = (uint *)indbuf->data;
for (int l = 0; l < NINDIRECT; l++) {
bfree(ip->dev, ind[l]);
ind[l] = 0;
}
brelse(indbuf);
bfree(ip->dev, dind[k]);
}
}
brelse(dinddbuf);
bfree(ip->dev, ip->addrs[NINDIRECT + 1]);
}
ip->size = 0;
iupdate(ip);
}

这段代码里有两个值得注意的地方:

1)一级索引块、二级索引块刚开始并没有被分配,只有在需要使用的时候才会被分配

2)在xv6中使用了缓冲区来减少I/O次数,一切对盘块的读写操作都首先在缓冲块上进行,并且为了维持磁盘中元数据(super block)的一致性,一切写盘块的操作都需要调用log_write将写操作添加到日志中。在bmap中涉及到了两种对盘块的写操作:对二级索引块的写操作(向其中添加表项,一个表项对应一个一级索引块)和对一级索引块的写操作(向其中添加表项,一个表项对应一个文件内容盘块)。虽然bmap也会分配指向文件内容的盘块,但对这个盘块的写操作并不是在bmap中进行的,而是在其他api中进行的,因此无需将这个块录入到日志中。

虽然struct dinodestruct inode中也包含了typemajornlinksize等成员,但读过相关的代码后你会发现,这些成员都无需修改,尤其是size这个成员很具有迷惑性,我们分配了盘块后这个成员看起来应该要增加的,但实际上这是一个没被用到的成员。毕竟xv6只是一个教学系统,不应对其苛求太多。这部分的分析都在我的关于file system的blog中有详细的说明。

Part1的相关代码也就这么多了,只要认真读下来file system相关的代码,并认真按照Hint来做,这就算是个白送的实验。不过比较要命的是usertests中的writebig测试,它会写一个MAXFILE大小的文件(添加大文件支持后,这个值从原来的2K变成了20W),会花费更长的测试时间。

xv6 kernel is booting

virtio disk init 0
init: starting sh
$ bigfile
..................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................
wrote 65803 blocks
bigfile done; ok

Part2 symlink

这部分实验要求我们为文件系统增添对符号链接(symbolic link)(也被称为软链接)的支持。为此我们首先简要了解一下xv6中的硬链接和软链接的含义。

在xv6中,每个文件拥有唯一的struct dinodestruct inode,更好的一种理解方法是,struct inode是一个文件控制块,存储着这个文件相关的元数据,一切对文件的读、写、打开、关闭、删除等操作都需要通过inode来完成。而很多时候,我们需要一个文件可以出现在多个目录下的支持(例如说多用户操作系统,每个用户都拥有一个自己的目录,它们希望共享某一文件)。如下图所示,这些文件的路径(链接)都导向了同一个inode

这种可以直接索引到inode的链接即是硬链接,本质上它是目录文件中的一个条目,根据这个条目,可以(准确的说是必定)获得该文件的inode。在xv6的api中,通过sys_link创建一个文件的硬链接,其中的核心api是 dirlink:

// Write a new directory entry (name, inum) into the directory dp.
int
dirlink(struct inode *dp, char *name, uint inum)
{
int off;
struct dirent de;
struct inode *ip; // Check that name is not present.
if((ip = dirlookup(dp, name, 0)) != 0){
iput(ip);
return -1;
} // Look for an empty dirent.
for(off = 0; off < dp->size; off += sizeof(de)){
if(readi(dp, 0, (uint64)&de, off, sizeof(de)) != sizeof(de))
panic("dirlink read");
if(de.inum == 0)
break;
} strncpy(de.name, name, DIRSIZ);
de.inum = inum;
if(writei(dp, 0, (uint64)&de, off, sizeof(de)) != sizeof(de))
panic("dirlink"); return 0;
}

sys_link中调用dirlink时,传入的参数inum为目标文件的inode编号,该编号被添加到目录表项中;这样我们也可以理解为什么调用ll时,会发现同一个文件的硬链接对应的inode号是相同的了;硬链接如果存在,那么其对应的inode必须存在inode通过维护一个nlink记录着硬链接的数量。当该inode新增一个硬链接时,其值便增1;当某个路径下的硬链接被删除时,nlink要减1,当值减至0时,说明该文件已经无法通过路径访问到,这时才能释放相应的文件。

软链接的定义则不同,我们可以看一下symlink的man page中的部分内容:

symlink() creates a symbolic link named linkpath which contains the string target.

Symbolic links are interpreted at run time as if the contents of the link had been substituted into the path being followed to find a file or directory.

A symbolic link (also known as a soft link) may point to an existing file or to a nonexistent one; the latter case is known as a dangling link.

软链接并不会增加nlink的数量,且可以指向一个不存在的路径。软链接也可以指向一个软链接,遇到这种情况时将会递归,直至找到相应的硬链接(或者达到最大递归深度后返回错误)。

在本Part中,要求我们实现以下内容:

1)实现一个新的系统调用 sys_symlink,通过该系统调用可以创建一个软链接;

2)为sys_open提供软链接的打开支持(通过软链接打开文件、O_NOFOLLOW);

只要理解了软链接的作用后,实现这个lab的思路也比较容易了,这个Part实现的思路也很多,我使用的是最简单最容易想到的:

1)将软链接本身也看做是一个文件,即软链接本身也有自己的dinodeinode,其文件内容也只是一行字符串,表征着软链接指向的文件的路径。创建软链接时,要为其分配一个inode

2)将软链接的inode添加到目录条目中,注意要避免命名冲突

uint64
sys_symlink(void)
{
char target[MAXPATH + 1], path[MAXPATH + 1], name[MAXPATH + 1];
memset((void *)target, 0, MAXPATH + 1);
memset((void *)path, 0, MAXPATH + 1); if (argstr(0, target, MAXPATH) < 0 || argstr(1, path, MAXPATH) < 0) {
return -1;
} begin_op(ROOTDEV);
// get parent inode
struct inode *iparent, *isym;
if ((iparent = nameiparent((char *)path, (char *)name)) == 0) {
end_op(ROOTDEV);
return -1;
}
// avoid name conflict
// do not hold ilock for iparent
uint off;
if ((isym = dirlookup(iparent, name, &off)) != 0) {
// printf("symlink name conflict with an existing file\n");
// iunlockput(iparent);
iput(iparent);
end_op(ROOTDEV);
return -1;
}
// allocate a dinode for symlink. isym is locked when it func create return
// after this operation, symlink entry is added under corresponding path
if ((isym = create(path, T_SYMLINK, 0, 0)) == 0) {
panic("create inode for symlink error"); // panic is not suitable, but simplify our situations.
}
// fill symlink file content with targetpath
int retval = 0;
uint pathlen = strlen((char *)target);
uint r, total;
r = total = 0;
while (total != pathlen) {
if ((r = writei(isym, 0, (uint64)(target + total), total, pathlen - total)) > 0) {
total += r;
} else {
retval = -1;
break;
}
}
// release
iunlockput(isym);
iput(iparent);
end_op(ROOTDEV);
return retval;
}

这样我们也可以理解为什么在一些文件系统上,同一个文件的软链接和硬链接的inode可能不同,因为软链接本身也是一个文件,有自己的inode,而硬链接只是一个目录中的条目,该条目索引到了对应的inode

然后修改sys_open,当发现打开的path对应的是一个软链接时,调用divesymlink,获得该软链接指向的文件的inode。如果使用了O_NOFOLLOW,说明我们希望打开的是软链接这个文件本身,一般fstate会使用这个flag:

uint64
sys_open(void)
{
char path[MAXPATH];
int fd, omode;
struct file *f;
struct inode *ip;
int n; if((n = argstr(0, path, MAXPATH)) < 0 || argint(1, &omode) < 0)
return -1; begin_op(ROOTDEV); if(omode & O_CREATE){
ip = create(path, T_FILE, 0, 0);
if(ip == 0){
end_op(ROOTDEV);
return -1;
}
} else {
if((ip = namei(path)) == 0){
end_op(ROOTDEV);
return -1;
}
ilock(ip);
if(ip->type == T_DIR && omode != O_RDONLY){
iunlockput(ip);
end_op(ROOTDEV);
return -1;
}
} /**************************
* SYMLINK *
*************************/
if (ip->type == T_SYMLINK && (omode & O_NOFOLLOW) == 0) {
ip = divesymlink(ip);
if (ip == 0) { // link target not exist
end_op(ROOTDEV);
return -1;
}
} if(ip->type == T_DEVICE && (ip->major < 0 || ip->major >= NDEV)){
iunlockput(ip);
end_op(ROOTDEV);
return -1;
} if((f = filealloc()) == 0 || (fd = fdalloc(f)) < 0){
if(f)
fileclose(f);
iunlockput(ip);
end_op(ROOTDEV);
return -1;
} if(ip->type == T_DEVICE){
f->type = FD_DEVICE;
f->major = ip->major;
f->minor = ip->minor;
} else {
f->type = FD_INODE;
}
f->ip = ip; // ref is here, so this function doesn't call iput
f->off = 0;
f->readable = !(omode & O_WRONLY);
f->writable = (omode & O_WRONLY) || (omode & O_RDWR); iunlock(ip);
end_op(ROOTDEV); return fd;
}

divesymlink的实现如下,其的参数为软链接文件的inode,返回软链接最终指向的文件的inode,如果这个文件不存在则返回0。根据Hint,divesymlink还要解决软链接之间的环路引用问题,这里我们简单的通过递归深度来判断,当深度大于10时,认为软链接最终指向的文件不存在:

static struct inode *
divesymlink(struct inode *isym)
{
struct inode *ip = isym;
char path[MAXPATH + 1];
uint len;
int deep = 0; do {
// get linked target
// we don't know how long the file str is, so we expect once read could get fullpath.
len = readi(ip, 0, (uint64)path, 0, MAXPATH);
if (readi(ip, 0, (uint64)(path + len), len, MAXPATH + 1) != 0)
panic("divesymlink : short read"); iunlockput(ip);
if (++deep > 10) { // may cycle link
return 0;
} if ((ip = namei((char *)path)) == 0) { // link target not exist
return 0;
}
ilock(ip);
} while (ip->type == T_SYMLINK);
return ip;
}

这样Part2也解决了。比较棘手的是api的锁获取和锁释放,以及iputiunlockputiunlock这三个api的选择。我在这里简单介绍一下:

1)iput会使ip->ref的数量减1(注意与ip->nlink区分开)。这个值记录着外部所持有struct inode指针的总数量。当这个值减小到0时,说明没有进程在访问这个文件,此时要将icache中对应的struct inode释放掉,但不会释放对应的文件。

2)iunlock会释放掉struct inode的锁,这把锁用来实现并发环境下的struct inode的原子访问,即我们在查看struct inode中的成员(type、data等)时,必须持有这把锁

3)iunlockput同时调用上述两个函数;

即如果我们不需要访问struct inode的成员时,应调用iunlock释放掉inode的锁,等需要访问时再拿起这把锁;当我们不再需要使用struct inode的指针时,要调用iput,弃用掉这个指针;

这样symlinktest也可以Pass了,对应的usertests也可以全pass:

xv6 kernel is booting

virtio disk init 0
init: starting sh
$ symlinktest
Start: test symlinks
test symlinks: ok
Start: test concurrent symlinks
test concurrent symlinks: ok
$ usertests
usertests starting
test reparent2: OK
test pgbug: OK
test sbrkbugs: usertrap(): unexpected scause 0x000000000000000c pid=3210
sepc=0x00000000000044b2 stval=0x00000000000044b2
usertrap(): unexpected scause 0x000000000000000c pid=3211
sepc=0x00000000000044b2 stval=0x00000000000044b2
OK
test badarg: OK
test reparent: OK
test twochildren: OK
test forkfork: OK
test forkforkfork: OK
test argptest: OK ......
......
......
test opentest: OK
test writetest: OK
test writebig: OK
test createtest: OK
test openiput: OK
test exitiput: OK
test iput: OK
test mem: OK
test pipe1: OK
test preempt: kill... wait... OK
test exitwait: OK
test rmdot: OK
test fourteen: OK
test bigfile: OK
test dirfile: OK
test iref: OK
test forktest: OK
test bigdir: OK
ALL TESTS PASSED

后记

这个Lab不算难,绝大多数时间要花费在读代码上。我这篇xv6 file system的学习笔记可能会比较帮助:

TODO: (在码了在码了,但愿今天能码完):

MIT 6.S081 Lab File System的更多相关文章

  1. 9. Lab: file system

    https://pdos.csail.mit.edu/6.S081/2021/labs/fs.html 1. Large files (moderate) 1.1 要求 Modify bmap() s ...

  2. MIT 6.S081 xv6调试不完全指北

    前言 今晚在实验室摸鱼做6.S081的Lab3 Allocator,并立下flag,改掉一个bug就拍死一只在身边飞的蚊子.在击杀8只蚊子拿到Legendary后仍然没能通过usertest,人已原地 ...

  3. MIT 6.S081 Lab5 Copy-On-Write Fork

    前言 最近绝大多数的空闲时间都拿来锤15-445了,很久没动6.S081.前几天回头看了一下一个月前锤完的Lazy Allocation,自己写的代码几乎都不认识了.......看来总结之类的东西最好 ...

  4. RH133读书 笔记(4) - Lab 4 System Services

    Lab 4 System Services Goal: Develop skills using system administration tools and setting up and admi ...

  5. RH253读书笔记(1)-Lab 1 System Monitoring

    Lab 1 System Monitoring Goal: To build skills to better assess system resources, performance and sec ...

  6. RH133读书笔记(11)-Lab 11 System Rescue and Troubleshooting

    Lab 11 System Rescue and Troubleshooting Goal: To build skills in system rescue procedures. Estimate ...

  7. MIT-6.828-JOS-lab5:File system, Spawn and Shell

    Lab 5: File system, Spawn and Shell tags: mit-6.828 os 概述 本lab将实现JOS的文件系统,只要包括如下四部分: 引入一个文件系统进程(FS进程 ...

  8. Storage System and File System Courses

    I researched a lot about storage system classes given at good universities this year. This had two r ...

  9. Can Microsoft’s exFAT file system bridge the gap between OSes?

    转自:http://arstechnica.com/information-technology/2013/06/review-is-microsofts-new-data-sharing-syste ...

随机推荐

  1. IT 界那些朗朗上口的“名言

    中国有很多古代警世名言,朗朗上口,凝聚了很多故事与哲理.硅谷的互联网公司里头也有一些这样的名言,凝聚了很多公司价值观和做事的方法,对于很多程序员来说,其影响潜移默化.这里收集了一些,如下. Stay ...

  2. 笔记本使用网线连接可以进行ftp下载,但是通过wifi连接只能登陆不能下载的问题。

    环境: (1)服务器为阿里云服务器,有公网ip,有内网ip,公网和内网已经做了相关端口的映射,ftp服务器为FileZilla,ftp服务器被动模式已开启,防火墙已关闭 (2)ftp客户端为java写 ...

  3. SpringMVC的@Validated校验注解使用方法

    validate会对参数进行校验,校验标准为validate后的类中的标准.本例中对User进行校验,User类中设置了校验标准. 在后台开发过程中,对参数的校验成为开发环境不可缺少的一个环节.比如参 ...

  4. 【JDBC核心】JDBC 概述

    JDBC 概述 数据的持久化 持久化(persistence):把数据保存到可掉电式存储设备中以供之后使用.大多数情况下,特别是企业级应用,数据持久化意味着将内存中的数据保存到硬盘上加以"固 ...

  5. 美业黑科技 ▏肌肤管家SkinRun V3S智能肌肤测试仪,实现“护肤”私人定制

    肌肤如同身体,也需要定时的"健康检查",但仅凭肉眼难以窥见深层的肌肤问题.而现在,肌肤管家SkinRun前沿黑科技护肤测试仪--SkinRun V3S便能帮助用户对症下药.肌肤管家 ...

  6. Linux 中软链接和硬链接的使用

    Linux 链接分两种,一种被称为硬链接(Hard Link),另一种被称为符号链接(Symbolic Link). 硬链接和软链接 硬链接 --- ln 要链接的文件 新硬链接名 软连接 --- l ...

  7. MySQL常用的一些(就几个)聚合函数

    聚合函数 (常用) 函数名称 描述 CONUT() 记数 SUM() 求和 AVG() 平均值 MAX() 最大值 MIN() 最小值 -- ================= 聚合函数 ====== ...

  8. ps ww

    [root@ma ~]# ps ww -p 1 PID TTY STAT TIME COMMAND 1 ? Ss 0:01 /sbin/init[root@ma ~]# ps -p 1 PID TTY ...

  9. 【MySQL】Last_SQL_Errno: 1594Relay log read failure: Could not parse relay log event entry...问题总结处理

    备库报错: Last_SQL_Errno: 1594 Last_SQL_Error: Relay log read failure: Could not parse relay log event e ...

  10. Doge.jpg 的背后是什么,你知道么?

    图片,是我们生活中最常见的信息载体,作为一个日常生活中无处不在的事物,我们已经很习惯静态或者动态的图片了.大家也了解静态图片主要是jpg/png格式,动态图片主要为 gif.那你有没有过一瞬间的疑惑: ...