<?xml version="1.0" encoding="UTF-8" ?>
<rss version="2.0"
xmlns:content="http://purl.org/rss/1.0/modules/content/"
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:atom="http://www.w3.org/2005/Atom"
xmlns:slash="http://purl.org/rss/1.0/modules/slash/">
<channel>
<title>NothAmor - Little Stone</title>
<link>https://nothamor.com/</link>
<description>Just So So ...</description>
<atom:link href="https://nothamor.com/rss.xml" rel="self" type="application/rss+xml" />
<lastBuildDate>Sat, 07 Mar 2026 02:11:58 +0800</lastBuildDate>
<item>
<title>深入浅出 Golang Runtime 源码分析</title>
<link>https://nothamor.com/archives/166.html</link>
<guid isPermaLink="false">https://nothamor.com/archives/166.html</guid>
<pubDate>Mon, 02 Mar 2026 13:10:00 +0800</pubDate>
<dc:creator>NothAmor</dc:creator>
<category><![CDATA[技术]]></category>
<description><![CDATA[# Golang Runtime 源码深度解析：从调度器到内存管理
引言
Go 语言以其简洁的语法和强大的并发能力赢得了众多开发者的青睐。然而，Go 的魅力远不止于此——其内置的 runtime 系统才是支撑这一切的核心引擎。本文将深入 Go runtim...]]></description>
<content:encoded><![CDATA[
<h1>Golang Runtime 源码深度解析：从调度器到内存管理</h1>
<h2>引言</h2>
<p>Go 语言以其简洁的语法和强大的并发能力赢得了众多开发者的青睐。然而，Go 的魅力远不止于此——其内置的 runtime 系统才是支撑这一切的核心引擎。本文将深入 Go runtime 的源码，带你一探究竟，理解这个精巧系统如何高效地管理 goroutine、内存分配和垃圾回收。</p>
<h2>1. Go Runtime 架构概览</h2>
<p>Go runtime 是 Go 程序运行时的核心组件，它负责：</p>
<ul>
<li>Goroutine 调度</li>
<li>内存分配与垃圾回收</li>
<li>网络轮询</li>
<li>系统调用封装</li>
<li>栈管理</li>
</ul>
<p>Runtime 的源码主要位于 <code>src/runtime</code> 目录下，其中最关键的几个文件包括：</p>
<ul>
<li><code>proc.go</code> - 调度器实现</li>
<li><code>mheap.go</code> - 内存分配器</li>
<li><code>mgc.go</code> - 垃圾回收器</li>
<li><code>netpoll.go</code> - 网络轮询器</li>
</ul>
<h2>2. Goroutine 调度器：GMP 模型</h2>
<h3>2.1 什么是 GMP？</h3>
<p>Go 的调度器采用 GMP 模型：</p>
<ul>
<li><strong>G (Goroutine)</strong>: 用户级轻量级线程</li>
<li><strong>M (Machine)</strong>: 操作系统线程</li>
<li><strong>P (Processor)</strong>: 逻辑处理器，包含 goroutine 队列</li>
</ul>
<p>这种设计巧妙地解决了传统线程模型的性能瓶颈问题。</p>
<h3>2.2 调度器工作原理</h3>
<p>当一个 goroutine 被创建时，它会被放入某个 P 的本地队列中。M 线程会从关联的 P 中获取 goroutine 来执行。如果本地队列为空，M 会尝试从其他 P 的队列中"偷取"工作（work-stealing）。</p>
<p>关键数据结构：</p>
<pre><code class="language-go">// G 结构体（简化版）
type g struct {
    stack       stack          // goroutine 栈
    sched       gobuf          // 调度信息
    goid        int64          // goroutine ID
    status      uint32         // 状态
}

// M 结构体（简化版）
type m struct {
    g0          *g             // 用于调度的特殊 goroutine
    curg        *g             // 当前正在执行的 goroutine
    p           puintptr       // 关联的 P
}

// P 结构体（简化版）
type p struct {
    runq        [256]guintptr  // 本地运行队列
    runnext     guintptr       // 下一个要运行的 goroutine
}</code></pre>
<h3>2.3 调度时机</h3>
<p>Go 调度器在以下时机进行调度：</p>
<ul>
<li><strong>系统调用</strong>：当 goroutine 进行系统调用时，M 可能会被阻塞，此时 runtime 会将 P 交给其他 M</li>
<li><strong>channel 操作</strong>：当 goroutine 在 channel 上阻塞时</li>
<li><strong>网络 I/O</strong>：通过 netpoller 实现非阻塞 I/O</li>
<li><strong>主动让出</strong>：通过 <code>runtime.Gosched()</code> 主动让出 CPU</li>
</ul>
<h2>3. 内存分配器</h2>
<p>Go 的内存分配器借鉴了 TCMalloc 的设计思想，采用多级缓存策略来减少锁竞争。</p>
<h3>3.1 内存分配层级</h3>
<p>Go 的内存分配分为三个层级：</p>
<ol>
<li><strong>mcache</strong>: 每个 P 对应一个 mcache，用于小对象分配，无锁</li>
<li><strong>mcentral</strong>: 全局中心缓存，按大小类组织，有锁但粒度较小</li>
<li><strong>mheap</strong>: 全局堆，管理大块内存和虚拟地址空间</li>
</ol>
<h3>3.2 分配流程</h3>
<p>当需要分配内存时：</p>
<ol>
<li>首先检查 mcache 中对应大小类的 span 是否有空闲对象</li>
<li>如果没有，从 mcentral 获取一个 span</li>
<li>如果 mcentral 也没有，从 mheap 分配新的 span</li>
</ol>
<p>关键函数 <code>mallocgc</code> 的简化逻辑：</p>
<pre><code class="language-go">func mallocgc(size uintptr, typ *_type, needzero bool) unsafe.Pointer {
    if size &lt;= maxSmallSize {
        // 小对象分配
        if noscan &amp;&amp; size &lt; maxTinySize {
            // 微小对象分配（&lt;16字节）
            return tinyAlloc(size, needzero)
        }
        // 小对象分配（16字节-32KB）
        return smallAlloc(size, needzero)
    }
    // 大对象分配（&gt;32KB）
    return largeAlloc(size, needzero)
}</code></pre>
<h2>4. 垃圾回收器</h2>
<p>Go 采用三色标记清除算法，实现了低延迟的并发垃圾回收。</p>
<h3>4.1 三色标记法</h3>
<ul>
<li><strong>白色</strong>: 未被访问的对象，垃圾回收后会被回收</li>
<li><strong>灰色</strong>: 已被访问但其引用对象未被扫描的对象</li>
<li><strong>黑色</strong>: 已被访问且其引用对象也已扫描的对象</li>
</ul>
<h3>4.2 GC 流程</h3>
<p>Go 的 GC 分为四个阶段：</p>
<ol>
<li><strong>Sweep Termination</strong>: 清理上一轮 GC 的残留</li>
<li><strong>Mark</strong>: 并发标记阶段，将可达对象标记为黑色</li>
<li><strong>Mark Termination</strong>: 完成标记，重新扫描栈</li>
<li><strong>Sweep</strong>: 并发清理白色对象</li>
</ol>
<h3>4.3 写屏障</h3>
<p>为了保证并发标记的正确性，Go 使用了 Dijkstra 写屏障：</p>
<pre><code class="language-go">// 写屏障伪代码
func writePointer(slot *unsafe.Pointer, ptr unsafe.Pointer) {
    *slot = ptr
    if gcphase == _GCmark || gcphase == _GCmarktermination {
        gcWriteBarrier(ptr)
    }
}</code></pre>
<p>写屏障确保在 GC 过程中，任何新建立的引用都会被正确标记。</p>
<h2>5. 栈管理</h2>
<p>Go 的 goroutine 栈是动态增长的，初始栈大小很小（2KB），根据需要自动扩展。</p>
<h3>5.1 栈扩容</h3>
<p>当函数调用需要更多栈空间时，runtime 会：</p>
<ol>
<li>分配更大的栈</li>
<li>复制原有栈内容到新栈</li>
<li>更新所有指向旧栈的指针</li>
</ol>
<h3>5.2 栈收缩</h3>
<p>在 Go 1.3 之前，栈只会增长不会收缩。从 Go 1.4 开始，引入了栈收缩机制，当栈使用率低于 1/4 时会尝试收缩。</p>
<h2>6. 网络轮询器</h2>
<p>Go 的网络轮询器基于 epoll (Linux)、kqueue (macOS) 或 IOCP (Windows) 实现，将阻塞的网络 I/O 转换为非阻塞操作。</p>
<h3>6.1 netpoller 工作原理</h3>
<ol>
<li>当 goroutine 执行网络读写时，如果数据不可用，会将 goroutine 挂起</li>
<li>netpoller 监控文件描述符的可读/可写状态</li>
<li>当文件描述符就绪时，netpoller 唤醒对应的 goroutine</li>
</ol>
<h3>6.2 关键数据结构</h3>
<pre><code class="language-go">// pollDesc 结构体（简化版）
type pollDesc struct {
    link   *pollDesc     // 链表链接
    fd     uintptr       // 文件描述符
    rg     guintptr      // 读等待者
    wg     guintptr      // 写等待者
}</code></pre>
<h2>7. 性能优化技巧</h2>
<p>基于对 runtime 的理解，我们可以写出更高效的 Go 代码：</p>
<h3>7.1 减少内存分配</h3>
<ul>
<li>使用 sync.Pool 复用对象</li>
<li>预分配切片容量</li>
<li>避免不必要的字符串转换</li>
</ul>
<h3>7.2 优化 goroutine 使用</h3>
<ul>
<li>避免创建过多的 goroutine</li>
<li>使用 worker pool 模式限制并发数</li>
<li>及时关闭不再需要的 goroutine</li>
</ul>
<h3>7.3 GC 调优</h3>
<ul>
<li>通过 <code>GOGC</code> 环境变量调整 GC 频率</li>
<li>使用 <code>debug.SetGCPercent()</code> 动态调整</li>
<li>监控 GC 统计信息：<code>runtime.ReadMemStats()</code></li>
</ul>
<h2>结论</h2>
<p>Go runtime 是一个精巧而复杂的系统，它通过 GMP 调度模型、分层内存分配、并发垃圾回收等机制，为我们提供了高效的并发编程体验。理解 runtime 的工作原理不仅能满足我们的好奇心，更能帮助我们写出更高效、更可靠的 Go 代码。</p>
<p>正如 Rob Pike 所说："Concurrency is not parallelism, it's about composition." Go runtime 正是这种哲学的最佳体现——它让我们能够优雅地组合并发原语，构建复杂的并发系统。</p>
<h2>参考资料</h2>
<ul>
<li>Go 源码: <a href="https://github.com/golang/go/tree/master/src/runtime">https://github.com/golang/go/tree/master/src/runtime</a></li>
<li>"Go 语言学习笔记" - 雨痕</li>
<li>"Go 程序设计语言" - Alan A. A. Donovan &amp; Brian W. Kernighan</li>
<li>Go Blog: <a href="https://blog.golang.org/">https://blog.golang.org/</a></li>
</ul>
]]></content:encoded>
<slash:comments>0</slash:comments>
<comments>https://nothamor.com/archives/166.html#comments</comments>
<enclosure url="https://cdn.nothamor.com/usr/uploads/2026/03/3667465910.jpg" length="0" type="image/jpeg" />
</item>
<item>
<title>oh-my-zsh默认主题显示全路径</title>
<link>https://nothamor.com/archives/156.html</link>
<guid isPermaLink="false">https://nothamor.com/archives/156.html</guid>
<pubDate>Thu, 29 Aug 2024 17:38:00 +0800</pubDate>
<dc:creator>NothAmor</dc:creator>
<category><![CDATA[技术]]></category>
<description><![CDATA[网上查的让我改PWD，但是改PWD只能一直显示一个路径，不会随当前的目录改变而改变
搞了一下分享出来
打开终端，输入如下指令
vi ~/.oh-my-zsh/themes/robbyrussell.zsh-theme
进入后按99dd，然后按i键后，粘贴如...]]></description>
<content:encoded><![CDATA[
<p>网上查的让我改PWD，但是改PWD只能一直显示一个路径，不会随当前的目录改变而改变</p>
<p>搞了一下分享出来</p>
<p>打开终端，输入如下指令</p>
<pre><code>vi ~/.oh-my-zsh/themes/robbyrussell.zsh-theme</code></pre>
<p>进入后按99dd，然后按i键后，粘贴如下内容</p>
<pre><code>PROMPT="%(?:%{$fg_bold[green]%}%1{➜%} :%{$fg_bold[red]%}%1{➜%} ) %{$fg[cyan]%}%~%{$reset_color%}"
PROMPT+=' $(git_prompt_info)'

ZSH_THEME_GIT_PROMPT_PREFIX="%{$fg_bold[blue]%}git:(%{$fg[red]%}"
ZSH_THEME_GIT_PROMPT_SUFFIX="%{$reset_color%} "
ZSH_THEME_GIT_PROMPT_DIRTY="%{$fg[blue]%}) %{$fg[yellow]%}%1{✗%}"
ZSH_THEME_GIT_PROMPT_CLEAN="%{$fg[blue]%})"</code></pre>
]]></content:encoded>
<slash:comments>0</slash:comments>
<comments>https://nothamor.com/archives/156.html#comments</comments>
<enclosure url="https://nothamor-1251700120.cos.ap-shanghai.myqcloud.com/2024/08/29/1724924302.jpg" length="0" type="image/jpeg" />
</item>
<item>
<title>Golang时间轮精简实现</title>
<link>https://nothamor.com/archives/golangTimeWheel.html</link>
<guid isPermaLink="false">https://nothamor.com/archives/golangTimeWheel.html</guid>
<pubDate>Sat, 03 Aug 2024 15:23:00 +0800</pubDate>
<dc:creator>NothAmor</dc:creator>
<category><![CDATA[技术]]></category>
<description><![CDATA[**文章背景**
最近遇到一个业务上的问题，用户进行一个操作，会同时生成两个kafka消息
在一个消费者消费消息的时候依赖另一个消费者生产的数据
被依赖方执行的速度比依赖方的慢，所以希望延迟一点消费这条数据
处理方法
kafka生产消息的时候可以加入一个d...]]></description>
<content:encoded><![CDATA[
<p><strong>文章背景</strong></p>
<p>最近遇到一个业务上的问题，用户进行一个操作，会同时生成两个kafka消息</p>
<p>在一个消费者消费消息的时候依赖另一个消费者生产的数据</p>
<p>被依赖方执行的速度比依赖方的慢，所以希望延迟一点消费这条数据</p>
<p><strong>处理方法</strong></p>
<p>kafka生产消息的时候可以加入一个delay参数，用于控制消息的延迟消费</p>
<p>但是这里的问题是生产者面对非常多的消费者，加入这个参数风险不可控</p>
<p>所以决定在希望延迟消费的消费者这里加入一个时间轮，用于实现延迟消费的功能</p>
<p>所以有了这篇文章</p>
<p><strong>时间轮代码</strong></p>
<pre><code class="language-go">package timewheel

import (
    "container/list"
    "fmt"
    "sync"
    "time"
)

type Timer struct {
    expiration time.Time
    task       func()
}

type TimeWheel struct {
    ticker      *time.Ticker  // 定时器
    slots       []*list.List  // 时间槽
    currentSlot int           // 当前时间槽
    slotCount   int           // 时间槽数量
    duration    time.Duration // 时间槽间隔
    lock        sync.Mutex    // 锁
}

// NewTimeWheel 创建时间轮
func NewTimeWheel(slotCount int, duration time.Duration) *TimeWheel {
    slots := make([]*list.List, slotCount)
    for i := range slots {
        slots[i] = list.New()
    }
    return &amp;TimeWheel{
        ticker:      time.NewTicker(duration),
        slots:       slots,
        slotCount:   slotCount,
        duration:    duration,
        currentSlot: 0,
    }
}

// AddTask 添加一个定时任务到时间轮
func (tw *TimeWheel) AddTask(delay time.Duration, task func()) {
    tw.lock.Lock()
    defer tw.lock.Unlock()

    expiration := time.Now().Add(delay)

    // 计算定时任务在时间轮中的到期时间, 添加到对应的时间槽
    ticks := int(delay / tw.duration)
    slotIndex := (tw.currentSlot + ticks) % tw.slotCount

    timer := &amp;Timer{expiration: expiration, task: task}
    tw.slots[slotIndex].PushBack(timer)
}

// Start 启动时间轮
func (tw *TimeWheel) Start() {
    go func() {
        defer func() {
            if err := recover(); err != nil {
                fmt.Println("timeWheel panic: ", err)
            }
        }()
        for range tw.ticker.C {
            tw.tickHandler()
        }
    }()
}

// Stop 停止时间轮
func (tw *TimeWheel) Stop() {
    tw.ticker.Stop()
}

func (tw *TimeWheel) tickHandler() {
    tw.lock.Lock()
    defer tw.lock.Unlock()

    slot := tw.slots[tw.currentSlot]
    tw.currentSlot = (tw.currentSlot + 1) % tw.slotCount

    for e := slot.Front(); e != nil; {
        next := e.Next()
        timer := e.Value.(*Timer)
        if timer.expiration.Before(time.Now()) || timer.expiration.Equal(time.Now()) {
            go timer.task()
            slot.Remove(e)
        }
        e = next
    }
}


</code></pre>
]]></content:encoded>
<slash:comments>2</slash:comments>
<comments>https://nothamor.com/archives/golangTimeWheel.html#comments</comments>
<enclosure url="https://nothamor-1251700120.cos.ap-shanghai.myqcloud.com/2024/08/03/1722669785.jpg" length="0" type="image/jpeg" />
</item>
<item>
<title>2024可用的docker镜像源</title>
<link>https://nothamor.com/archives/docker2024.html</link>
<guid isPermaLink="false">https://nothamor.com/archives/docker2024.html</guid>
<pubDate>Tue, 02 Jul 2024 10:38:00 +0800</pubDate>
<dc:creator>NothAmor</dc:creator>
<category><![CDATA[技术]]></category>
<description><![CDATA[docker daemon.json配置：
{
    "registry-mirrors": [
        "https://docker.1panel.live",
        "https://hub.rat.dev"
    ]
}...]]></description>
<content:encoded><![CDATA[
<p>docker daemon.json配置：</p>
<pre><code>{
    "registry-mirrors": [
        "https://docker.1panel.live",
        "https://hub.rat.dev"
    ]
}</code></pre>
]]></content:encoded>
<slash:comments>2</slash:comments>
<comments>https://nothamor.com/archives/docker2024.html#comments</comments>
<enclosure url="https://nothamor-1251700120.cos.ap-shanghai.myqcloud.com/2024/07/02/1719887898.jpg" length="0" type="image/jpeg" />
</item>
<item>
<title>Golang八股面试题+大厂面经</title>
<link>https://nothamor.com/archives/144.html</link>
<guid isPermaLink="false">https://nothamor.com/archives/144.html</guid>
<pubDate>Thu, 11 Jan 2024 17:13:00 +0800</pubDate>
<dc:creator>NothAmor</dc:creator>
<category><![CDATA[技术]]></category>
<description><![CDATA[Golang面试题
https://nothamor-1251700120.cos.ap-shanghai.myqcloud.com/Go%E9%9D%A2%E8%AF%95%E9%A2%98.pdf
Golang大厂面经
https://nothamor...]]></description>
<content:encoded><![CDATA[
<p>Golang面试题</p>
<p><a href="https://nothamor-1251700120.cos.ap-shanghai.myqcloud.com/Go%E9%9D%A2%E8%AF%95%E9%A2%98.pdf">https://nothamor-1251700120.cos.ap-shanghai.myqcloud.com/Go%E9%9D%A2%E8%AF%95%E9%A2%98.pdf</a></p>
<p>Golang大厂面经</p>
<p><a href="https://nothamor-1251700120.cos.ap-shanghai.myqcloud.com/Go%20%E5%A4%A7%E5%8E%82%E9%9D%A2%E7%BB%8F.zip">https://nothamor-1251700120.cos.ap-shanghai.myqcloud.com/Go%20%E5%A4%A7%E5%8E%82%E9%9D%A2%E7%BB%8F.zip</a></p>
]]></content:encoded>
<slash:comments>3</slash:comments>
<comments>https://nothamor.com/archives/144.html#comments</comments>
<enclosure url="https://nothamor-1251700120.cos.ap-shanghai.myqcloud.com/2024/01/11/1704964373.jpg" length="0" type="image/jpeg" />
</item>
<item>
<title>Golang Gin框架中实现Server Sent Events(SSE)</title>
<link>https://nothamor.com/archives/golangGinSSE.html</link>
<guid isPermaLink="false">https://nothamor.com/archives/golangGinSSE.html</guid>
<pubDate>Tue, 02 Jan 2024 10:18:00 +0800</pubDate>
<dc:creator>NothAmor</dc:creator>
<category><![CDATA[技术]]></category>
<description><![CDATA[被这个功能困扰了好久，一直没实现，因为网上的Gin框架实现的SSE都非常复杂，写成了一整套复杂的中间件，经过研究，写出了一套精简的实现。
SSE接口代码, 发送信息修改sendEvents内代码即可：
func ChatStreamSSE(ctx cont...]]></description>
<content:encoded><![CDATA[
<p>被这个功能困扰了好久，一直没实现，因为网上的Gin框架实现的SSE都非常复杂，写成了一整套复杂的中间件，经过研究，写出了一套精简的实现。</p>
<p>SSE接口代码, 发送信息修改sendEvents内代码即可：</p>
<pre><code>func ChatStreamSSE(ctx context.Context, c *gin.Context, req vo.ChatCompletionRequest) {
event := Event{
    ConversationId: uuid.New().String(),
    IsFinish:       false,
    IsReasoning:    false,
}

stream, err := openai.NewChatCompletionStream(ctx, req)
if err != nil {
    logger.Ex(ctx, tag, "NewChatCompletionStream:%+v", err)
    event.Content = fmt.Sprintf("new chat completion stream failed. err:[%+v]", err)
    event.IsFinish = true
    c.SSEvent("data", event)
    c.Writer.Flush()
    return
}
defer stream.Close()

for {
    streamData, streamErr := stream.Recv()
    if streamErr != nil &amp;&amp; !errors.Is(streamErr, io.EOF) &amp;&amp; streamErr.Error() != "EOF" {
        logger.Ex(ctx, tag, "stream data error: %v", streamErr)
        event.Content = fmt.Sprintf("stream data error: %v", streamErr)
        event.IsFinish = true
        c.SSEvent("data", event)
        c.Writer.Flush()
    }
    c.SSEvent("data", event)
    c.Writer.Flush()
}

return
}</code></pre>
]]></content:encoded>
<slash:comments>2</slash:comments>
<comments>https://nothamor.com/archives/golangGinSSE.html#comments</comments>
<enclosure url="https://nothamor-1251700120.cos.ap-shanghai.myqcloud.com/2024/01/02/1704162847.png" length="0" type="image/jpeg" />
</item>
<item>
<title>驾校一点通 - 理论考试爬虫</title>
<link>https://nothamor.com/archives/DriverLicenseQuestionsSpider.html</link>
<guid isPermaLink="false">https://nothamor.com/archives/DriverLicenseQuestionsSpider.html</guid>
<pubDate>Mon, 12 Jul 2021 19:04:00 +0800</pubDate>
<dc:creator>NothAmor</dc:creator>
<category><![CDATA[技术]]></category>
<description><![CDATA[[github repo="NothAmor/DriverLicenseQuestionsSpider" /]
因为写过了很多爬虫项目，临近科目四考试，在驾考宝典刷题的时候就在想能不能把这些题目全都爬取下来，百度搜了一下发现了这个驾校一点通，因为有题目接口...]]></description>
<content:encoded><![CDATA[
<p>[github repo="NothAmor/DriverLicenseQuestionsSpider" /]</p>
<p>因为写过了很多爬虫项目，临近科目四考试，在驾考宝典刷题的时候就在想能不能把这些题目全都爬取下来，百度搜了一下发现了这个驾校一点通，因为有题目接口，而且完全没有反爬机制，一晚上就搞定了</p>
<p>直接存储所有题目到mysql数据库里，图片存储到本地文件夹内，如果这个文件夹在网站内，可以直接修改imageurl为网址存储到数据库里</p>
<p>然后写完了之后我也不知道有啥用，水篇博客吧</p>
]]></content:encoded>
<slash:comments>1</slash:comments>
<comments>https://nothamor.com/archives/DriverLicenseQuestionsSpider.html#comments</comments>
<enclosure url="https://nothamor-1251700120.cos.ap-shanghai.myqcloud.com/blog/typecho/E5cm9EPXIAMkyoi.jpg" length="0" type="image/jpeg" />
</item>
<item>
<title>不支持M.2接口的旧主板使用M.2 SSD作为开机启动盘的三种方法</title>
<link>https://nothamor.com/archives/old_masterboard_use_nvme.html</link>
<guid isPermaLink="false">https://nothamor.com/archives/old_masterboard_use_nvme.html</guid>
<pubDate>Fri, 18 Jun 2021 19:34:00 +0800</pubDate>
<dc:creator>NothAmor</dc:creator>
<category><![CDATA[技术]]></category>
<description><![CDATA[## 前言
之所以会有这个文章是因为云计算赛项中获得了国赛二等奖, 发了奖金就给老电脑更新一点配件, 暂时买了一个NVME M.2转换PCI-E的转接卡,  金士顿骇客神条DDR3 8GB 1600 * 2, 希捷2TB 5900转硬盘, 影驰256GB ...]]></description>
<content:encoded><![CDATA[
<h2>前言</h2>
<p>之所以会有这个文章是因为云计算赛项中获得了国赛二等奖, 发了奖金就给老电脑更新一点配件, 暂时买了一个NVME M.2转换PCI-E的转接卡,  金士顿骇客神条DDR3 8GB 1600 * 2, 希捷2TB 5900转硬盘, 影驰256GB NVME M.2固态硬盘</p>
<h2>起因</h2>
<p>最初我以为旧电脑中, 虽然没有NVME M.2的接口, 但是买一个转接卡转给PCI-E作为启动盘, 是没有什么问题的, 但是当我给SSD装好系统, 插上主板之后, 发现BIOS中无法选择PCI-E上的硬盘作为启动设备, 特征为不认盘, 就是在启动菜单中不显示PCI-E槽中的SSD, 经过笔者一整天的思考, 发现了三种方法解决这一个问题, 让没有M.2接口的旧主板作为启动设备</p>
<h2>三种解决方案</h2>
<ol>
<li>使用Clover这一类boot工具, 在进入boot manager后再选择SSD进行启动</li>
<li>手动修改主板BIOS, 导入NVME模块, 使BIOS支持PCI-E硬盘引导</li>
<li>将M.2 SSD转为USB, 开机使用USB作为启动设备</li>
</ol>
<h2>解决方案一: Clover</h2>
<p>这里可以直接参考这个大佬的文章, 写的很详细</p>
<p><a href="https://www.bilibili.com/read/cv5496032/">【教程】使用Clover启动Nvme协议的固态硬盘 - 哔哩哔哩 (bilibili.com)</a></p>
<h2>解决方案二: 更新主板BIOS</h2>
<p><strong>事先提醒, 刷BIOS有风险</strong></p>
<p>参考文章: <a href="https://www.52pojie.cn/thread-1136840-1-1.html">mmtool——BIOS修改工具及添加nvme模块教程 - 『精品软件区』 - 吾爱破解 - LCG - LSG |安卓破解|病毒分析|www.52pojie.cn</a></p>
<p>如果你的主板是B85-PLUS R2.0, 那么可以用我在下面提供的BIOS进行操作, 这个是我亲测没问题的</p>
<p><a href="https://cdn.nothamor.com/B85-PLUS-R2.0-NVME(Z97).zip">https://cdn.nothamor.com/B85-PLUS-R2.0-NVME(Z97).zip</a></p>
<p>这个包的操作方法:</p>
<ol>
<li>
<p>准备一个空U盘, 新建一个BIOS文件夹, 将包中的三个文件放入其中</p>
</li>
<li>
<p>准备一个PE启动U盘/DVD, 进入纯DOS模式(一定要纯DOS模式, 大白菜就有纯DOS)</p>
</li>
<li>
<p>进入纯DOS模式后, 按照以下操作</p>
<p>首先进入C盘, 输入: <code>C:\</code>后回车</p>
<p>输入 <code>dir</code>后回车, 查看C盘下文件, 如果有BIOS文件夹, 则是正确, 如果没有BIOS文件夹, 依次输入 <code>D:\</code>等, 直到你找到BIOS文件夹</p>
<p>输入 <code>cd BIOS</code>回车进入BIOS文件夹下, 再输入 <code>dir</code>查看当前目录下的所有文件, 如果有 <code>AFUDOS.exe, NVME.ROM, START.bat</code>三个文件的话, 则为正确, 直接输入 <code>START.bat</code>回车进行BIOS的刷写, 然后等待之后BIOS就刷写好了</p>
<p>刷写好BIOS后输入 <code>r</code>回车, 进行重新启动, 这时候进入BIOS, 会发现启动设备多了一个Windows Boot Manager, 则为成功</p>
</li>
<li>
<p>这时候工作还没有做完, SSD中的系统必须按照如下要求安装</p>
<p>进入PE系统, 使用Disk Genius分区工具, 将PCI-E槽中的SSD硬盘进行格式化(格式化前备份好资料)</p>
<p>格式化后, 选中SSD硬盘, 选择上方的快速分区, 分区表类型选择 <code>GUID</code></p>
<p>下方的分区数目可以自定义, 想要分几个区, 就选几个区, 一般这个SSD都是用于引导系统, 所以分一个区就够了, 给它全部空间</p>
<p>在下方选择 <code>重建主引导记录(MBR)</code>, 在右侧高级设置中分配分区空间</p>
<p>一定要勾选右下角的 <code>对齐分区到此扇区数的整数倍</code>, 给默认值就好(2048扇区)</p>
<p>然后确定, 这时候就分区好了</p>
<p>只有PCI-E槽上的SSD需要这么分区, 其它硬盘按照正常分区方法进行分区就可以</p>
</li>
<li>
<p>在分区之后, 使用Disk Genius工具清除 <code>除了ESP, MSR分区</code>以外的 <code>所有分区盘符</code>(就是C盘, D盘这些东西, 先给清掉)</p>
</li>
<li>
<p>然后再使用Disk Genius工具, <code>给那些分区重新分配盘符</code>, 但是 <code>一定要给SSD的那个分区C盘</code>, 其它的自己分就好 <code>(ESP, MSR不用管)</code></p>
</li>
<li>
<p>然后使用老毛桃PE中的 <code>WinNTSetUp工具</code>, 进行操作系统的安装, 位置在 <code>开始菜单-&gt;所有程序-&gt;安装维护</code>中, Windows安装源选择官方Windows10镜像, 引导驱动器选择刚才的ESP分区(这个ESP分区一定要在Disk Genius中和文件管理器中确认好), 安装驱动器选择C盘, 然后单击开始安装就好了</p>
</li>
<li>
<p>安装完系统后进行重启, 重启后一定要进入BIOS确认好Windows Boot Manager在第一启动项, 确认好后按F10保存并重启</p>
</li>
<li>
<p>重启后就进行系统的配置了, 这时候就已经结束了所有步骤了, Enjoy it!</p>
</li>
</ol>
<h2>解决方案三: M.2转USB</h2>
<p>事先声明, 这个方案只是笔者想到的, 但是没有实践, 因为我使用解决方案二解决了我的问题, 但是我认为这个解决方案应该是可行的</p>
<p>就是淘宝买一个支持M.2转USB3.0的转接器, 然后插在电脑上, BIOS中选择USB启动, 选择这个USB, 应该没有问题</p>
<p>但是SSD的速度有可能被影响, 这个笔者没有试验过</p>
]]></content:encoded>
<slash:comments>6</slash:comments>
<comments>https://nothamor.com/archives/old_masterboard_use_nvme.html#comments</comments>
<enclosure url="https://nothamor-1251700120.cos.ap-shanghai.myqcloud.com/E3Bs_8WXoAsOwYy.jpg" length="0" type="image/jpeg" />
</item>
<item>
<title>自动播放传智播客课程视频</title>
<link>https://nothamor.com/archives/auto_play_chuanzhi_video.html</link>
<guid isPermaLink="false">https://nothamor.com/archives/auto_play_chuanzhi_video.html</guid>
<pubDate>Mon, 22 Jun 2020 22:56:00 +0800</pubDate>
<dc:creator>NothAmor</dc:creator>
<category><![CDATA[技术]]></category>
<description><![CDATA[天天让看视频做那个作业, 打游戏的时候还要盯着时长, 回来切视频
太麻烦了, 干脆写了个脚本自动帮我切换, 如果有习题就会播放语音提醒
(一点小提示, 可以配合tampermonkey的H5播放器控制来实现16倍速播放, 畅享极致丝滑, 几秒一个视频, 我...]]></description>
<content:encoded><![CDATA[
<p>天天让看视频做那个作业, 打游戏的时候还要盯着时长, 回来切视频
太麻烦了, 干脆写了个脚本自动帮我切换, 如果有习题就会播放语音提醒
(一点小提示, 可以配合tampermonkey的H5播放器控制来实现16倍速播放, 畅享极致丝滑, 几秒一个视频, 我也是听我朋友说的传智不计观看视频时长, 如果计视频观看时长给分数的话就GG了, 酌情使用)</p>
<p><strong>使用方法:</strong>
在传智播客视频播放页按F12, 将下面的代码粘贴到控制台里面, 回车即可运行
(本项目已在GitHub开源, 如果对你有用的话, 顺路给个starrrrrr吧!)
[github repo="NothAmor/auto_chuanzhi" /]</p>
<pre><code>console.log("欢迎使用传智自动播放插件, 作者博客:https://www.nothamor.com");
    setTimeout(function() {
        let url = window.location.href;
        if(url.includes("http://stu.ityxb.com/lookPaper/busywork/")) {
            auto_search();
            console.log("检测到为测试页面, 开始自动查询题目");
        } else if(url.includes("http://stu.ityxb.com/preview/detail/")) {
            auto_play();
            console.log("检测到为视频播放页面, 开始自动播放视频");
        }
    }, 5000);

    function auto_play() {
        const CLASS_LIST = document.getElementsByClassName("point-progress-box");
        const CLASS_NAME = document.getElementsByClassName("point-text ellipsis");
        let question_text = document.getElementsByTagName("pre")[0];
        let player = document.getElementsByTagName("video")[0].id;
        let question_text_value;
        document.getElementById(player).click();
        let counter = 0;
        const TIMER = setInterval(function () {
            let percent = CLASS_LIST[counter].innerHTML.replace(/\ +/g, "").replace(/[\r\n]/g, "");
            let title_name = CLASS_NAME[counter].innerHTML.replace(/\ +/g, "").replace(/[\r\n]/g, "");
            if (percent.includes("100%") &amp;&amp; counter == (CLASS_LIST.length - 1)) {
                clearInterval(TIMER);
                alert("当前页所有视频均已播放完成");
            } else if (percent.includes("100%")) {
                CLASS_LIST[counter + 1].click();
                player = document.getElementsByTagName("video")[0].id;
                document.getElementById(player).click();
                counter++;
            }
            if (title_name.includes("习题")) {
                question_text = document.getElementsByTagName("pre")[0];
                question_text_value = question_text.innerHTML;
                console.log(" ");
                GM_xmlhttpRequest({
                    method: 'GET',
                    url: 'http://jb.s759n.cn/chati.php?w=' + encodeURIComponent(QUESTION[counter].innerHTML),
                    headers: {
                        'Content-type': 'application/x-www-form-urlencoded',
                    },
                    data: 'q=' + encodeURIComponent(QUESTION[counter].innerHTML),
                    onload: function (response) {
                        if (response.status == 200) {
                            let obj = $.parseJSON(response.responseText.replace(/^操作数据失败！/, '')) || {};
                            obj.answer = obj.data;
                            console.log("题目:" + QUESTION[counter].innerHTML + "的答案为:" + obj.answer);
                            if (obj.code) {
                            } else {
                                console.log('服务器繁忙，正在重试...');
                            }
                        } else if (response.status == 403) {
                            console.log('请求过于频繁，建议稍后再试');
                        } else {
                            console.log('服务器异常，正在重试...');
                        }
                    }
                });
            }
        }, 1000);
    }
    function auto_search() {
        const QUESTION = document.getElementsByTagName("pre");
        let counter = 0;
        const SEARCH = setInterval(function() {
            GM_xmlhttpRequest({
                method: 'GET',
                url: 'http://jb.s759n.cn/chati.php?w=' + encodeURIComponent(QUESTION[counter].innerHTML),
                headers: {
                    'Content-type': 'application/x-www-form-urlencoded',
                },
                onload: function (response) {
                    if (response.status == 200) {
                        let obj = $.parseJSON(response.responseText.replace(/^操作数据失败！/, '')) || {};
                        console.log("第" + counter + "题" + "的答案为:" + obj.data);
                        if (obj.code) {
                        } else {
                            console.log('服务器繁忙，正在重试...');
                        }
                    } else if (response.status == 403) {
                        console.log('请求过于频繁，建议稍后再试');
                    } else {
                        console.log('服务器异常，正在重试...');
                    }
                }
            });
            counter++;
            if(counter == (QUESTION.length)) {
                clearInterval(SEARCH);
                console.log("题目搜索完成");
            }
        }, 1000);
    }</code></pre>
<p>当然还有另外一个版本, 这个依赖于浏览器插件tampermonkey, 不用每次都手动去输入脚本内容
可以手动添加, 也可以直接在greasy fork上下载本脚本
greasy fork下载链接:<a href="https://greasyfork.org/zh-CN/scripts/405920-%E4%BC%A0%E6%99%BA%E8%87%AA%E5%8A%A8%E6%92%AD%E6%94%BE%E8%A7%86%E9%A2%91">https://greasyfork.org/zh-CN/scripts/405920-传智自动播放视频</a></p>
<pre><code>// ==UserScript==
// @name         传智自动播放视频
// @namespace    http://tampermonkey.net/
// @version      0.3
// @description  自动播放传智播客课程视频, 开发者博客:http://www.nothamor.cn
// @author       nothamor
// @match        *.ityxb.com/*
// @grant        GM_xmlhttpRequest
// ==/UserScript==

(function() {
    'use strict';

    console.log("欢迎使用传智自动播放插件, 作者博客:https://www.nothamor.cn");
    setTimeout(function() {
        let url = window.location.href;
        if(url.includes("http://stu.ityxb.com/lookPaper/busywork/")) {
            auto_search();
            console.log("检测到为测试页面, 开始自动查询题目");
        } else if(url.includes("http://stu.ityxb.com/preview/detail/")) {
            auto_play();
            console.log("检测到为视频播放页面, 开始自动播放视频");
        }
    }, 5000);

    function auto_play() {
        const CLASS_LIST = document.getElementsByClassName("point-progress-box");
        const CLASS_NAME = document.getElementsByClassName("point-text ellipsis");
        let question_text = document.getElementsByTagName("pre")[0];
        let player = document.getElementsByTagName("video")[0].id;
        let question_text_value;
        document.getElementById(player).click();
        let counter = 0;
        const TIMER = setInterval(function () {
            let percent = CLASS_LIST[counter].innerHTML.replace(/\ +/g, "").replace(/[\r\n]/g, "");
            let title_name = CLASS_NAME[counter].innerHTML.replace(/\ +/g, "").replace(/[\r\n]/g, "");
            if (percent.includes("100%") &amp;&amp; counter == (CLASS_LIST.length - 1)) {
                clearInterval(TIMER);
                alert("当前页所有视频均已播放完成");
            } else if (percent.includes("100%")) {
                CLASS_LIST[counter + 1].click();
                player = document.getElementsByTagName("video")[0].id;
                document.getElementById(player).click();
                counter++;
            }
            if (title_name.includes("习题")) {
                question_text = document.getElementsByTagName("pre")[0];
                question_text_value = question_text.innerHTML;
                console.log(" ");
                GM_xmlhttpRequest({
                    method: 'GET',
                    url: 'http://jb.s759n.cn/chati.php?w=' + encodeURIComponent(QUESTION[counter].innerHTML),
                    headers: {
                        'Content-type': 'application/x-www-form-urlencoded',
                    },
                    data: 'q=' + encodeURIComponent(QUESTION[counter].innerHTML),
                    onload: function (response) {
                        if (response.status == 200) {
                            let obj = $.parseJSON(response.responseText.replace(/^操作数据失败！/, '')) || {};
                            obj.answer = obj.data;
                            console.log("题目:" + QUESTION[counter].innerHTML + "的答案为:" + obj.answer);
                            if (obj.code) {
                            } else {
                                console.log('服务器繁忙，正在重试...');
                            }
                        } else if (response.status == 403) {
                            console.log('请求过于频繁，建议稍后再试');
                        } else {
                            console.log('服务器异常，正在重试...');
                        }
                    }
                });
            }
        }, 1000);
    }
    function auto_search() {
        const QUESTION = document.getElementsByTagName("pre");
        let counter = 0;
        const SEARCH = setInterval(function() {
            GM_xmlhttpRequest({
                method: 'GET',
                url: 'http://jb.s759n.cn/chati.php?w=' + encodeURIComponent(QUESTION[counter].innerHTML),
                headers: {
                    'Content-type': 'application/x-www-form-urlencoded',
                },
                onload: function (response) {
                    if (response.status == 200) {
                        let obj = $.parseJSON(response.responseText.replace(/^操作数据失败！/, '')) || {};
                        console.log("第" + counter + "题" + "的答案为:" + obj.data);
                        if (obj.code) {
                        } else {
                            console.log('服务器繁忙，正在重试...');
                        }
                    } else if (response.status == 403) {
                        console.log('请求过于频繁，建议稍后再试');
                    } else {
                        console.log('服务器异常，正在重试...');
                    }
                }
            });
            counter++;
            if(counter == (QUESTION.length)) {
                clearInterval(SEARCH);
                console.log("题目搜索完成");
            }
        }, 1000);
    }
})();</code></pre>
]]></content:encoded>
<slash:comments>55</slash:comments>
<comments>https://nothamor.com/archives/auto_play_chuanzhi_video.html#comments</comments>
<enclosure url="https://cdn.nothamor.com/v2-d90a4aa914c8570fe7fbd51baaafad3b_r.jpg" length="0" type="image/jpeg" />
</item>
<item>
<title>Monika After Story(MAS)汉化问题反馈</title>
<link>https://nothamor.com/archives/mas_problems_feedback.html</link>
<guid isPermaLink="false">https://nothamor.com/archives/mas_problems_feedback.html</guid>
<pubDate>Sat, 02 May 2020 22:12:00 +0800</pubDate>
<dc:creator>NothAmor</dc:creator>
<category><![CDATA[其它]]></category>
<description><![CDATA[此版本汉化不再继续维护, 有团队汉化了最新的MAS, 请移步[https://forum.nothamor.cn/thread-645-1-1.html][1]查看
博主是负责汉化的一份子, 如果发布的汉化有问题, 可以在评论区反馈, 第一时间予以解决

...]]></description>
<content:encoded><![CDATA[
<p>此版本汉化不再继续维护, 有团队汉化了最新的MAS, 请移步<a href="https://forum.nothamor.cn/thread-645-1-1.html">https://forum.nothamor.cn/thread-645-1-1.html</a>查看</p>
<pre><code>博主是负责汉化的一份子, 如果发布的汉化有问题, 可以在评论区反馈, 第一时间予以解决

**毕业论文问题反馈修复, 错误发现时间2020-05-18, 问题所在:script-topics.rpyc文件, line:10071**
问题原因:{w=0.5没有结尾花括号导致的语法问题
修复后的文件下载链接: [https://pan.baidu.com/s/1xmbyxPnyCH3KhONyB-bSNw][2] 提取码: r9dw
修复文件使用方法:将rpyc文件放入游戏目录下的game目录中替换

**grammer文件问题反馈修复, 错误发现时间2020-05-27, 问题所在:script-grammer.rpyc文件, line:363**
问题原因:变量名编写错误
修复后的文件下载链接:[https://pan.baidu.com/s/1emavYrI82WZKzwpJ7NBbpQ][3] 提取码: 1di3
修复文件使用方法:将rpyc文件放入游戏目录下的game目录中替换

**补上新的不限速链接：**
[https://mtr-mirror.coding.net/api/share/download/38e78295-b6a1-4906-af18-505218f37727][4]
[https://mtr-mirror.coding.net/api/share/download/9fd1b919-1978-4977-9328-2319cf815f8e][5]
[https://mtr-mirror.coding.net/api/share/download/8e7175fa-a467-4d51-a5a9-420610375766][6]
[https://mtr-mirror.coding.net/api/share/download/993b71f7-26a1-4f2e-bc2f-26157e293e57][7]
[https://mtr-mirror.coding.net/api/share/download/38728c43-4e83-4add-be89-e1f4694b7d7f][8]</code></pre>
]]></content:encoded>
<slash:comments>71</slash:comments>
<comments>https://nothamor.com/archives/mas_problems_feedback.html#comments</comments>
<enclosure url="https://cdn.nothamor.com/ytQ3jxO2Wqhapmg.jpg" length="0" type="image/jpeg" />
</item>
</channel>
</rss>
