操作系统也是 CRUD - Jiajun的编程随想

原文:https://jiajunhuang.com/articles/2020_06_15-unix_crud.md.html

起初学习UNIX环境下的编程觉得非常难,毕竟这是一个陌生的东西,但是工作后,照面打得多了,自然就熟悉了些,在此总结一些经验。

其实要是写了几年代码的人,回头总结一下用代码一般都做什么事,会得到四个字母:CRUD,也就是增删查改。

  • 后端程序员:对数据库的增删查改
  • 系统程序员:对OS数据结构的增删查改
  • 嵌入式程序员:对硬件数据的增删查改
  • 前端程序员:对JSON和用户输入的增删查改

那既然大家归根结底,都是增删查改,学习UNIX是否可以从另外一个角度来看待呢?

Andrew S·Tanenbaum 现代操作系统

开始之前

远古时期,所有程序员都是直接操纵硬件的,但是这有很大的问题,你也想用100M内存,我也想用100M,但是一共只有100M,怎么办? 你也想播放音乐,我也想,又怎么办?你也想写磁盘,我也想写磁盘,怎么办?

除了上述冲突,更麻烦的是,所有人都要知道所有硬件怎么操作,才能写出一个程序,这太难了。于是有了操作系统,它帮我们屏蔽了所有的硬件设备的 区别,提供了统一的接口,并且抽象了一些东西,比如文件系统,通过文件系统,我们不再需要操心这个文件是写到磁盘具体的哪个位置, 而只需要告诉操作系统:我要写文件,写什么,写多长即可。

这一切都是通过操作系统提供的系统调用(system call)来完成的。

我们还是要了解一下UNIX操作系统的结构:

UNIX 架构

可以看到,最内部的,就是内核,它帮我们处理各种各样的硬件设备,然后抽象成一个简单的概念。 比如我们不用担心是HDD还是SSD,我们只管用文件系统,我们只需要调用操作文件的系统调用,操作系统会帮我们找到对应的磁盘位置并且把数据写进去, 即使失败了,也会返回一个错误告诉我们为什么。

稍往外层,就是系统调用,系统调用的外层有shell和library routines,另外applications也可以和它直接接触,这是什么意思呢?

shell可以简单理解为命令行,但是用熟了之后,应当知道,bash,zsh这类才是shell,他封装了很多系统调用,然后以命令或者图形界面的方式提供 给用户,比如Windows上,很多按钮点一下,背后其实就调用了一些系统调用。 而library routines,比如glibc,帮我们封装了系统调用,加了一些代码,让我们调用更加轻松,因为我们的代码不可能是通过执行各种命令行 来完成,那样抗不住并发,所以我们得通过调用函数的方式, 我们可以直接调用系统调用,这就是图中applications直接和system call接触的那部分,也可以调用library routines,然后后者帮我们调用system call。

模块分类

相信很多人毕业设计做的就是XXX管理系统,内核其实也是一个XXX管理系统,只不过它是多个管理系统的集合:

  • 文件管理系统
  • 用户管理系统
  • 权限管理系统
  • 进程管理系统
  • 内存管理系统
  • 硬件设备管理系统

其实内核自己就可以做这些事情,但是因为每个人每个公司的需求都不一样,所以内核暴露了系统调用来让程序员可以借助它的能力, 做定制化操作。

内核为了安全,会通过CPU的能力,将进程分为两个类别:

  • 在内核里跑的
  • 在用户空间跑的

显然前者拥有更大的权利,干啥都可以,后者的权力自然是受限的。

文件管理系统

我们知道,XXX管理系统的主要内容就是CRUD。我们来看看,我们自然是要:

  • 创建文件(增)
  • 删除文件(删)
  • 查找文件内容(查)
  • 更新文件内容(写入)

由于除了文件,还有文件夹,所以它也有CRUD。由于UNIX中把一切都抽象成文件,因此,对他们也有CRUD,不过,幸好由于UNIX把一切都抽象成文件,因此 对一切文件,也就这么几个系统调用(其实是C库封装过后的库函数,但是其底层是系统调用,后同):

  • creat 创建文件
  • remove 删除文件
  • close 关闭文件
  • read 读取文件
  • write 写文件
  • lseek 设置写入的位置

当然,UNIX发展这么久了,其历史分支多的不得了,自然就会有分裂和不统一的情况,因此除了上述系统调用,你还会看到一些标新立异、特殊使用的,比如针对网络的 recvsend,没有关系,理解一下,毕竟几十年 的系统了。

说起文件,特别重要的一个概念就是文件描述符(file descriptor,简称fd)。这是啥呢?你看我们平时操作数据库的每一行,是不是还有个Primary ID?对的,这玩意儿就跟它类似,用于定位一个文件,这样大家都知道我们说的是哪个文件。

为了高性能,我们的文件管理系统自然就要引入缓存,引入缓存,那就要引入把缓存写到磁盘的操作,因此就有了:

  • sync
  • fsync
  • fdatasync

另外文件还有一些属性什么的,因此就有了:

  • stat
  • fstat
  • lstat

权限管理系统和用户管理系统

UNIX是一个多用户的系统,既然是多用户,那就要做好权限控制,我不能随意看别人的东西,别人也不能随意看我的,要不然大家岂不是都在 裸奔了吗?

首先要做的就是在文件系统里引入权限控制,也就同时引入了用户管理系统。

UNIX中,每个用户都有自己的id,每个用户都可以加入多个组,每个组也可以有多个用户,因此UNIX把权限分为三类:

  • 对本人的控制
  • 对本组的控制
  • 对其他人(也就是其他组)的控制

然后控制,又细分为三个:

  • 可读
  • 可写
  • 可执行(对于文件来说是可执行,对于文件夹来说,就是是否可以列出其下文件)

由于UNIX设计的时候,内存都是很宝贵的(今天没那么贵了但是也还挺宝贵的),所以用位来保存权限,可读、可写、可执行分别是二进制的 00000100,00000010和00000001,因此对应10进制分别是4,2,1。

注意,4+2+1 = 7。

所以如果你给一个文件,权限为777,那么就是对本人可读可写可执行,对组内成员可读可写可执行,对其他所有人可读可写可执行。

如果是467,那就是对本人可读,对组内成员可读可写,对其他人可读可写可执行。

以此类推。

当然,我们得有系统调用来对权限进行CRUD:

  • access
  • chmod 改、增、删

刚才说了,有用户和组这两个概念。UNIX下,用户信息存储在 /etc/passwd 上,组的信息存储在 /etc/group 上。

用户有密码,以前密码也是存在 /etc/passwd 上,不过不太安全,后来就把密码以更严格的文件权限存储在 /etc/shadow 里。

$ ll -h /etc/passwd /etc/shadow /etc/group
-rw-r--r-- 1 root root    910 Jun 12 15:29 /etc/group
-rw-r--r-- 1 root root   2.2K Jun 12 15:29 /etc/passwd
-rw-r----- 1 root shadow 1.3K Jun 12 15:29 /etc/shadow

进程管理系统和内存管理系统

我们的代码,最终编译为程序,它就是一堆0和1,躺在磁盘里。但是当它被操作系统加载,运行之后,在内存里,就叫进程。当然,进程也得有一个唯一ID,我们管他叫 pid(process id)

进程和内存分不开,进程要申请内存、销毁内存等,因此就有了这些CRUD:

  • malloc 申请内存
  • free 释放内存

至于改和查嘛,这是在代码逻辑里就做好了的,无需操心。

而对进程本身的一些增删查改,则是对它的运行环境什么的操作,比如:

  • getenv 获取环境变量
  • getpid 获取当前进程id
  • getppid 获取父进程id
  • getrlimitsetrlimit 获取和设置进程的一些资源限制
  • fork 创建新的进程
  • exec 替换当前进程所要执行的代码
  • wait 等待子进程

进程管理本身就还包含很多东西,比如进程所属的用户和组,进程的资源限制,进程之间的通信,进程的优先级等等,每一个子系统都有它的CRUD。

后来又引入了线程这个概念,线程是CPU调度的最小单位,同一个进程内的多个线程, 共享所在进程里的一些资源,对于这些东西,又有很多CRUD函数。同时还引入了并发和并行这两个概念,因此也就有一些锁、竞争等概念,所以又有一批 对应的CRUD函数。

硬件管理系统

程序要做的很重要的一件事情,其实是和其他进程通信,和其他硬件设备打交道。

同步I/O无法应对并发,因此就有了I/O多路复用,也就有了:

  • select
  • epoll
  • kqueue

还有POSIX规定的aio系列函数,这又是一堆CRUD。

总结

一个操作系统,要做的事情很多,直到今天,Linux内核也还在非常活跃的开发维护中,操作系统要做的事情远远不止上面所列的这么 几个管理系统,但是我们可以把操作系统要做的事情,分门别类来看,就会发现,原来它也是CRUD。当然,这个CRUD的技术含量,可就 比较高了。


git创建、提交、同步到远程分支

假设本地目前所在分支为 v1.0,我们需要基于该分支创建v1.1、推送到远端并且同步。

git checkout -b v1.1
git push origin v1.1

关联本地与远端分支:

git branch --set-upstream-to=origin/v1.1 v1.1

另一台开发机应当如下操作:

git fetch --all

可以看到有提示,从远端 fetch 到了新的分支,然后查看远端分支列表:

git branch -r

拉取并切换到新分支,或在本地分支基础上直接关联本地分支:

git checkout --track origin/v1.1
git branch --set-upstream-to=origin/v1.1 v1.1

laravel nova 的侧边栏元素排序

自 nova 2.10开始,nova增加了一个权重字段,用于侧边栏排序使用:

/**
 * The side nav menu order.
 *
 * @var int
 */
public static $priority = 2;

配置后在 NovaServiceProvider中声明使用自定义排序即可:

public function boot()
{
    Nova::sortResourcesBy(function ($resource) {
        return $resource::$priority ?? 9999;
    });
}

相关的代码参考 nova 的测试也可以找到:

# ./nova/tests/Feature/NovaTest.php
    public function test_can_specify_user_sortable_closure_for_sorting()
    {
        $callback = function ($resource) {
            return $resource::$priority;
        };

        Nova::sortResourcesBy($callback);

        $this->assertEquals($callback, Nova::$sortCallback);

        Nova::sortResourcesBy(function ($resource) {
            return $resource::label();
        });
    }

参考资料


恢复 Mac idea 默认配置

可能是年久失修吧,Mac下的 idea 的 ideavim 插件无法使用了,即使卸载重装也没办法解决。

网上找了彻底恢复 idea 的配置方法,如下:

rm -rf ~/Library/Preferences/idea文件夹/
rm -rf ~/Library/Caches/idea文件夹/

在 windows上使用 wireguard 进行内网组网

下载好了官网的软件,正常进行握手了,在本地竟然没法 ping 通内网IP?这篇文章记录我如何打通内网的。

  1. 确认目前正常连接

    确认一直在握手,连接没有问题

    1592289512043

  2. 确认网卡已正常创建

    确实已经正常创建了。

    1592289578530

  3. 查看Windows路由表

    route print
    

    1592289443085

    在这我发现了,本地只有当前IP 100.100.100.4的路由,没有其它IP的路由。

  4. 手动添加路由

    启动管理员权限的cmd命令行,将内网网段添加一条路由

    route add 100.100.100.0 mask 255.255.255.0 100.100.100.4 metric 261
    

    其中metric 261 是参考已有的那条 100.100.100.4的路由写的。

    1592289753256

  5. 验证成功!

    ping 100.100.100.1