mirror of
https://github.com/CoderSherlock/CoderSherlock.github.io.git
synced 2026-06-13 08:08:10 -07:00
1025 lines
40 KiB
HTML
1025 lines
40 KiB
HTML
<!DOCTYPE html><html lang="en">
|
||
<head><meta charset="utf-8">
|
||
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||
<meta name="viewport" content="width=device-width, initial-scale=1, user-scalable=no"><title>Xv6 introduction - Stop Talking, Start Doing</title>
|
||
|
||
<meta name="description" content="In this post, you will learn a few basic concepts of xv6. Learning path will be closed coupled to first project assignment I gave when I assisted in teaching...">
|
||
<link rel="canonical" href="http://localhost:4000/posts/intro-xv6"><link rel="alternate" type="application/rss+xml" title="Stop Talking, Start Doing" href="/feed.xml"><!-- start favicons snippet, use https://realfavicongenerator.net/ --><link rel="apple-touch-icon" sizes="180x180" href="/assets/apple-touch-icon.png"><link rel="icon" type="image/png" sizes="32x32" href="/assets/favicon-32x32.png"><link rel="icon" type="image/png" sizes="16x16" href="/assets/favicon-16x16.png"><link rel="manifest" href="/assets/site.webmanifest"><link rel="mask-icon" href="/assets/safari-pinned-tab.svg" color="#fc4d50"><link rel="shortcut icon" href="/assets/favicon.ico">
|
||
|
||
<meta name="msapplication-TileColor" content="#ffc40d"><meta name="msapplication-config" content="/assets/browserconfig.xml">
|
||
|
||
<meta name="theme-color" content="#ffffff">
|
||
<!-- end favicons snippet --><link rel="stylesheet" href="/assets/css/main.css"><link rel="stylesheet" href="https://cdn.bootcdn.net/ajax/libs/font-awesome/5.15.1/css/all.css" ><!-- start custom head snippets -->
|
||
|
||
<!-- end custom head snippets -->
|
||
<script>(function() {
|
||
window.isArray = function(val) {
|
||
return Object.prototype.toString.call(val) === '[object Array]';
|
||
};
|
||
window.isString = function(val) {
|
||
return typeof val === 'string';
|
||
};
|
||
|
||
window.hasEvent = function(event) {
|
||
return 'on'.concat(event) in window.document;
|
||
};
|
||
|
||
window.isOverallScroller = function(node) {
|
||
return node === document.documentElement || node === document.body || node === window;
|
||
};
|
||
|
||
window.isFormElement = function(node) {
|
||
var tagName = node.tagName;
|
||
return tagName === 'INPUT' || tagName === 'SELECT' || tagName === 'TEXTAREA';
|
||
};
|
||
|
||
window.pageLoad = (function () {
|
||
var loaded = false, cbs = [];
|
||
window.addEventListener('load', function () {
|
||
var i;
|
||
loaded = true;
|
||
if (cbs.length > 0) {
|
||
for (i = 0; i < cbs.length; i++) {
|
||
cbs[i]();
|
||
}
|
||
}
|
||
});
|
||
return {
|
||
then: function(cb) {
|
||
cb && (loaded ? cb() : (cbs.push(cb)));
|
||
}
|
||
};
|
||
})();
|
||
})();
|
||
(function() {
|
||
window.throttle = function(func, wait) {
|
||
var args, result, thisArg, timeoutId, lastCalled = 0;
|
||
|
||
function trailingCall() {
|
||
lastCalled = new Date;
|
||
timeoutId = null;
|
||
result = func.apply(thisArg, args);
|
||
}
|
||
return function() {
|
||
var now = new Date,
|
||
remaining = wait - (now - lastCalled);
|
||
|
||
args = arguments;
|
||
thisArg = this;
|
||
|
||
if (remaining <= 0) {
|
||
clearTimeout(timeoutId);
|
||
timeoutId = null;
|
||
lastCalled = now;
|
||
result = func.apply(thisArg, args);
|
||
} else if (!timeoutId) {
|
||
timeoutId = setTimeout(trailingCall, remaining);
|
||
}
|
||
return result;
|
||
};
|
||
};
|
||
})();
|
||
(function() {
|
||
var Set = (function() {
|
||
var add = function(item) {
|
||
var i, data = this._data;
|
||
for (i = 0; i < data.length; i++) {
|
||
if (data[i] === item) {
|
||
return;
|
||
}
|
||
}
|
||
this.size ++;
|
||
data.push(item);
|
||
return data;
|
||
};
|
||
|
||
var Set = function(data) {
|
||
this.size = 0;
|
||
this._data = [];
|
||
var i;
|
||
if (data.length > 0) {
|
||
for (i = 0; i < data.length; i++) {
|
||
add.call(this, data[i]);
|
||
}
|
||
}
|
||
};
|
||
Set.prototype.add = add;
|
||
Set.prototype.get = function(index) { return this._data[index]; };
|
||
Set.prototype.has = function(item) {
|
||
var i, data = this._data;
|
||
for (i = 0; i < data.length; i++) {
|
||
if (this.get(i) === item) {
|
||
return true;
|
||
}
|
||
}
|
||
return false;
|
||
};
|
||
Set.prototype.is = function(map) {
|
||
if (map._data.length !== this._data.length) { return false; }
|
||
var i, j, flag, tData = this._data, mData = map._data;
|
||
for (i = 0; i < tData.length; i++) {
|
||
for (flag = false, j = 0; j < mData.length; j++) {
|
||
if (tData[i] === mData[j]) {
|
||
flag = true;
|
||
break;
|
||
}
|
||
}
|
||
if (!flag) { return false; }
|
||
}
|
||
return true;
|
||
};
|
||
Set.prototype.values = function() {
|
||
return this._data;
|
||
};
|
||
return Set;
|
||
})();
|
||
|
||
window.Lazyload = (function(doc) {
|
||
var queue = {js: [], css: []}, sources = {js: {}, css: {}}, context = this;
|
||
var createNode = function(name, attrs) {
|
||
var node = doc.createElement(name), attr;
|
||
for (attr in attrs) {
|
||
if (attrs.hasOwnProperty(attr)) {
|
||
node.setAttribute(attr, attrs[attr]);
|
||
}
|
||
}
|
||
return node;
|
||
};
|
||
var end = function(type, url) {
|
||
var s, q, qi, cbs, i, j, cur, val, flag;
|
||
if (type === 'js' || type ==='css') {
|
||
s = sources[type], q = queue[type];
|
||
s[url] = true;
|
||
for (i = 0; i < q.length; i++) {
|
||
cur = q[i];
|
||
if (cur.urls.has(url)) {
|
||
qi = cur, val = qi.urls.values();
|
||
qi && (cbs = qi.callbacks);
|
||
for (flag = true, j = 0; j < val.length; j++) {
|
||
cur = val[j];
|
||
if (!s[cur]) {
|
||
flag = false;
|
||
}
|
||
}
|
||
if (flag && cbs && cbs.length > 0) {
|
||
for (j = 0; j < cbs.length; j++) {
|
||
cbs[j].call(context);
|
||
}
|
||
qi.load = true;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
};
|
||
var load = function(type, urls, callback) {
|
||
var s, q, qi, node, i, cur,
|
||
_urls = typeof urls === 'string' ? new Set([urls]) : new Set(urls), val, url;
|
||
if (type === 'js' || type ==='css') {
|
||
s = sources[type], q = queue[type];
|
||
for (i = 0; i < q.length; i++) {
|
||
cur = q[i];
|
||
if (_urls.is(cur.urls)) {
|
||
qi = cur;
|
||
break;
|
||
}
|
||
}
|
||
val = _urls.values();
|
||
if (qi) {
|
||
callback && (qi.load || qi.callbacks.push(callback));
|
||
callback && (qi.load && callback());
|
||
} else {
|
||
q.push({
|
||
urls: _urls,
|
||
callbacks: callback ? [callback] : [],
|
||
load: false
|
||
});
|
||
for (i = 0; i < val.length; i++) {
|
||
node = null, url = val[i];
|
||
if (s[url] === undefined) {
|
||
(type === 'js' ) && (node = createNode('script', { src: url }));
|
||
(type === 'css') && (node = createNode('link', { rel: 'stylesheet', href: url }));
|
||
if (node) {
|
||
node.onload = (function(type, url) {
|
||
return function() {
|
||
end(type, url);
|
||
};
|
||
})(type, url);
|
||
(doc.head || doc.body).appendChild(node);
|
||
s[url] = false;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
};
|
||
return {
|
||
js: function(url, callback) {
|
||
load('js', url, callback);
|
||
},
|
||
css: function(url, callback) {
|
||
load('css', url, callback);
|
||
}
|
||
};
|
||
})(this.document);
|
||
})();
|
||
</script><script>
|
||
(function() {
|
||
var TEXT_VARIABLES = {
|
||
version: '2.2.6',
|
||
sources: {
|
||
font_awesome: 'https://cdn.bootcdn.net/ajax/libs/font-awesome/5.15.1/css/all.css',
|
||
jquery: 'https://cdn.bootcss.com/jquery/3.1.1/jquery.min.js',
|
||
leancloud_js_sdk: '//cdn.jsdelivr.net/npm/leancloud-storage@3.13.2/dist/av-min.js',
|
||
chart: 'https://cdn.bootcss.com/Chart.js/2.7.2/Chart.bundle.min.js',
|
||
gitalk: {
|
||
js: 'https://cdn.bootcss.com/gitalk/1.2.2/gitalk.min.js',
|
||
css: 'https://cdn.bootcss.com/gitalk/1.2.2/gitalk.min.css'
|
||
},
|
||
valine: 'https://unpkg.com/valine/dist/Valine.min.js',
|
||
mathjax: 'https://cdn.bootcss.com/mathjax/2.7.4/MathJax.js?config=TeX-MML-AM_CHTML',
|
||
mermaid: 'https://cdn.bootcss.com/mermaid/8.0.0-rc.8/mermaid.min.js'
|
||
},
|
||
site: {
|
||
toc: {
|
||
selectors: 'h1,h2,h3'
|
||
}
|
||
},
|
||
paths: {
|
||
search_js: '/assets/search.js'
|
||
}
|
||
};
|
||
window.TEXT_VARIABLES = TEXT_VARIABLES;
|
||
})();
|
||
</script>
|
||
</head>
|
||
<body>
|
||
<div class="root" data-is-touch="false">
|
||
<div class="layout--page js-page-root"><div class="page__main js-page-main page__viewport has-aside cell cell--auto">
|
||
|
||
<div class="page__main-inner"><div class="page__header d-print-none"><header class="header"><div class="main">
|
||
<div class="header__title">
|
||
<div class="header__brand"><svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||
width="24px" height="24px" viewBox="0 0 24 24">
|
||
<style type="text/css">
|
||
.st0{fill:#515151;}
|
||
</style>
|
||
<path class="st0" d="M1.7,22.3c5.7-5.7,11.3-5.7,17,0c3.3-3.3,3.5-5.3,0.8-6c2.7,0.7,3.5-1.1,2.3-5.6s-3.3-5.2-6.3-2.1
|
||
c3-3,2.3-5.2-2.1-6.3S7,1.8,7.7,4.6C7,1.8,5,2.1,1.7,5.3C7.3,11,7.3,16.7,1.7,22.3"/>
|
||
</svg>
|
||
<a title="My personal blog, with some boring research staff and some tricks I was fancy to. I'll try my best to make this blog fun and useful. Not just a place I complain about all happens in my Lab.
|
||
" href="/">Stop Talking, Start Doing</a></div><button class="button button--secondary button--circle search-button js-search-toggle"><i class="fas fa-search"></i></button></div><nav class="navigation">
|
||
<ul><li class="navigation__item"><a href="/archive.html">Archive</a></li><li class="navigation__item"><a href="/about.html">About</a></li><li><button class="button button--secondary button--circle search-button js-search-toggle"><i class="fas fa-search"></i></button></li></ul>
|
||
</nav></div>
|
||
</header>
|
||
</div><div class="page__content"><div class ="main"><div class="grid grid--reverse">
|
||
|
||
<div class="col-aside d-print-none js-col-aside"><aside class="page__aside js-page-aside"><div class="toc-aside js-toc-root"></div>
|
||
</aside></div>
|
||
|
||
<div class="col-main cell cell--auto"><!-- start custom main top snippet -->
|
||
|
||
<!-- end custom main top snippet -->
|
||
<article itemscope itemtype="http://schema.org/Article"><div class="article__header"><header><h1>Xv6 introduction</h1></header><span class="split-space"> </span>
|
||
<a class="edit-on-github"
|
||
title="Edit on Github"
|
||
href="https://github.com/CoderSherlock/CoderSherlock.github.io/tree/master/_posts/2017-07-08-intro-xv6.md">
|
||
<i class="far fa-edit"></i></a></div><meta itemprop="headline" content="Xv6 introduction"><div class="article__info clearfix"><ul class="left-col menu"><li>
|
||
<a class="button button--secondary button--pill button--sm"
|
||
href="/archive.html?tag=xv6">xv6</a>
|
||
</li></ul><ul class="right-col menu"><li><i class="far fa-calendar-alt"></i> <span>Jul 28, 2017</span>
|
||
</li></ul></div><meta itemprop="author" content="Pengzhan Hao"/><meta itemprop="datePublished" content="2017-07-28T14:56:55-04:00">
|
||
<meta itemprop="keywords" content="xv6"><div class="js-article-content"><div class="layout--article"><!-- start custom article top snippet -->
|
||
|
||
<!-- end custom article top snippet -->
|
||
<div class="article__content" itemprop="articleBody"><p>In this post, you will learn a few basic concepts of xv6. Learning path will be closed coupled to first project assignment I gave when I assisted in teaching OS classes.
|
||
Understand system call and know how to implement a simple one will be coved as the first half.
|
||
In the second half of this post, I will discuss a little bit more on how to debug xv6 using gdb.</p>
|
||
|
||
<h2 id="xv6-systemcall">Xv6 Systemcall</h2>
|
||
|
||
<p>To invoke a system call, we have to first define a user mode function to be the interface of the kernel instruction in file <em>user.h</em>.</p>
|
||
|
||
<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kt">void</span> <span class="nf">function</span> <span class="p">(</span><span class="kt">void</span><span class="p">);</span>
|
||
</code></pre></div></div>
|
||
|
||
<p>This interface-like function will then pass the function name, in this case function, to <em>usys.S</em>. When using user mode function in programs, <em>usys.S</em> will generate a reference to SYS_function and push system call number of this function into %eax. After that, system can know from <em>syscall.c</em> and determining whether this system call is available. We must define same name system function and add it into <em>syscall.h</em> and <em>syscall.c</em>.</p>
|
||
|
||
<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="cp">#define SYS_function ## // ## is the system call number
|
||
</span><span class="p">[</span><span class="n">SYS_function</span><span class="p">]</span> <span class="n">sys_function</span> <span class="c1">// real system function name</span>
|
||
<span class="k">extern</span> <span class="kt">int</span> <span class="nf">sys_function</span><span class="p">(</span><span class="kt">void</span><span class="p">);</span> <span class="c1">// real system function declaration</span>
|
||
</code></pre></div></div>
|
||
|
||
<p>After adding these sentences to syscall files, we can implement real function in specific place where you want to make the function works well.</p>
|
||
|
||
<p>Sometimes, we need to pass variables among system calls. In this case, variables’ values are not necessary and even can’t be pass directly into system_function. When invoke a system call function, all variables of this system call will be pushed into current process’ stack. In file <em>syscall.c</em>, multiple functions are provided to get these variables from the process. I won’t waste time on explaining how to use these functions especially when elegant and detailed comments were written in source codes. However, I will explain concepts and how process organized and works in xv6 in future articles.</p>
|
||
|
||
<h2 id="debug-xv6-with-gdb">Debug xv6 with gdb</h2>
|
||
|
||
<p>Please make sure that you have used gdb before.
|
||
If you never used gdb, you may write a simple 50-100 lines c code and practice how to use gdb first.</p>
|
||
|
||
<ul>
|
||
<li><a href="https://sourceware.org/gdb/current/onlinedocs/gdb/">GDB Manual</a></li>
|
||
<li><a href="https://darkdust.net/files/GDB%20Cheat%20Sheet.pdf">GDB cheatsheet (pdf)</a></li>
|
||
</ul>
|
||
|
||
<p>To make sure xv6 gdb enabled, please check if <em>.gdbinit.tmpl</em> file exist.
|
||
This file is used for generate <em>.gdbinit</em> file which you can late consider it as a configuration for gdb.</p>
|
||
|
||
<p>Before running the xv6 instance in QEMU, one more thing you need to know is that using gdb to debug xv6 must be attached remotely.
|
||
This is because xv6 was running within QEMU, and emulator is virtually gapped from the host device.
|
||
Later when you start debugging, QEMU will open a gdb server to let gdb client connect to.</p>
|
||
|
||
<p>Once you want to start, using following command to compile and run xv6</p>
|
||
|
||
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">$ </span>make qemu-nox-gdb
|
||
<span class="k">***</span> Now run <span class="s1">'gdb'</span><span class="nb">.</span>
|
||
qemu-system-i386 <span class="nt">-nographic</span> <span class="nt">-drive</span> <span class="nv">file</span><span class="o">=</span>fs.img,index<span class="o">=</span>1,media<span class="o">=</span>disk,format<span class="o">=</span>raw <span class="nt">-drive</span> <span class="nv">file</span><span class="o">=</span>xv6.img,index<span class="o">=</span>0,media<span class="o">=</span>disk,format<span class="o">=</span>raw <span class="nt">-smp</span> 2 7
|
||
</code></pre></div></div>
|
||
|
||
<p>At this moment, it feels xv6 was stuck, this is because QEMU is ready to be connected by the gdb client.
|
||
You may use the <em>.gdbinit</em> to automatically finish this remote connection by simple typein following command in another terminal.</p>
|
||
|
||
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">$ </span>gdb <span class="nt">-x</span> .gdbinit
|
||
GNU gdb <span class="o">(</span>Debian 8.2.1-2+b3<span class="o">)</span> 8.2.1
|
||
|
||
...
|
||
|
||
The target architecture is assumed to be i8086
|
||
<span class="o">[</span>f000:fff0] 0xffff0: ljmp <span class="nv">$0x3630</span>,<span class="nv">$0xf000e05b</span>
|
||
0x0000fff0 <span class="k">in</span> ?? <span class="o">()</span>
|
||
+ symbol-file kernel
|
||
warning: A handler <span class="k">for </span>the OS ABI <span class="s2">"GNU/Linux"</span> is not built into this configuration
|
||
of GDB. Attempting to <span class="k">continue </span>with the default i8086 settings.
|
||
|
||
<span class="o">(</span>gdb<span class="o">)</span>
|
||
</code></pre></div></div>
|
||
|
||
<p>Now within this gdb client shell, type ‘c’ to continue the xv6, and you will see xv6 start execution in the first terminal.</p>
|
||
|
||
<p>At this moment, you may add breakpoints to your code to see if your code is correctly implemented or not.</p>
|
||
|
||
<p><strong>One more thing</strong>, if you open <em>.gdbinit</em> file, you’ll find that it by default connect to a localhost target.
|
||
If you are working on some other environment that target and client were not placed in the same device, change the localhost to ip address correspondingly.
|
||
Using ssh may connect to different physical devices under same domain name, this is because load balancer were used. To check ip address, search command <em>ip</em>.</p>
|
||
|
||
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>target remote localhost:28467
|
||
<span class="c"># target remote [ip-addr]:28467</span>
|
||
</code></pre></div></div>
|
||
</div><section class="article__sharing d-print-none"></section><div class="d-print-none"><footer class="article__footer"><meta itemprop="dateModified" content="2017-07-28T14:56:55-04:00"><!-- start custom article footer snippet -->
|
||
|
||
<!-- end custom article footer snippet -->
|
||
<div class="article__subscribe"><div class="subscribe"><i class="fas fa-rss"></i> <a type="application/rss+xml" href="/feed.xml">Subscribe</a></div>
|
||
</div><div class="article__license"></div></footer>
|
||
<div class="article__section-navigator clearfix"><div class="previous"><span>PREVIOUS</span><a href="/posts/some-of-my-previews-exper-work">Some of my previews experiment works: 2016</a></div><div class="next"><span>NEXT</span><a href="/posts/generate-word-cloud-with-chinese-fenci">Generate Word Cloud Figures with Chinese-Tokenization and WordCloud python libraries</a></div></div></div>
|
||
|
||
</div>
|
||
|
||
<script>(function() {
|
||
var SOURCES = window.TEXT_VARIABLES.sources;
|
||
window.Lazyload.js(SOURCES.jquery, function() {
|
||
$(function() {
|
||
var $this ,$scroll;
|
||
var $articleContent = $('.js-article-content');
|
||
var hasSidebar = $('.js-page-root').hasClass('layout--page--sidebar');
|
||
var scroll = hasSidebar ? '.js-page-main' : 'html, body';
|
||
$scroll = $(scroll);
|
||
|
||
$articleContent.find('.highlight').each(function() {
|
||
$this = $(this);
|
||
$this.attr('data-lang', $this.find('code').attr('data-lang'));
|
||
});
|
||
$articleContent.find('h1[id], h2[id], h3[id], h4[id], h5[id], h6[id]').each(function() {
|
||
$this = $(this);
|
||
$this.append($('<a class="anchor d-print-none" aria-hidden="true"></a>').html('<i class="fas fa-anchor"></i>'));
|
||
});
|
||
$articleContent.on('click', '.anchor', function() {
|
||
$scroll.scrollToAnchor('#' + $(this).parent().attr('id'), 400);
|
||
});
|
||
});
|
||
});
|
||
})();
|
||
</script>
|
||
</div><section class="page__comments d-print-none"></section></article><!-- start custom main bottom snippet -->
|
||
|
||
<!-- end custom main bottom snippet -->
|
||
</div>
|
||
</div></div></div><div class="page__footer d-print-none">
|
||
<footer class="footer py-4 js-page-footer">
|
||
<div class="main"><div itemscope itemtype="http://schema.org/Person">
|
||
<meta itemprop="name" content="Pengzhan Hao"><meta itemprop="url" content="/"><div class="footer__author-links"><div class="author-links">
|
||
<ul class="menu menu--nowrap menu--inline"><li title="Send me an Email.">
|
||
<a class="button button--circle mail-button" itemprop="email" href="mailto:haopengzhan@gmail.com" target="_blank">
|
||
<i class="fas fa-envelope"></i>
|
||
</a><li title="Follow me on Github.">
|
||
<a class="button button--circle github-button" itemprop="sameAs" href="https://github.com/codersherlock" target="_blank">
|
||
<div class="icon"><svg fill="#000000" width="24px" height="24px" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg">
|
||
<path class="svgpath" data-index="path_0" fill="#272636" d="M0 525.2c0 223.6 143.3 413.7 343 483.5 26.9 6.8 22.8-12.4 22.8-25.4l0-88.7c-155.3 18.2-161.5-84.6-172-101.7-21.1-36-70.8-45.2-56-62.3 35.4-18.2 71.4 4.6 113.1 66.3 30.2 44.7 89.1 37.2 119 29.7 6.5-26.9 20.5-50.9 39.7-69.6C248.8 728.2 181.7 630 181.7 513.2c0-56.6 18.7-108.7 55.3-150.7-23.3-69.3 2.2-128.5 5.6-137.3 66.5-6 135.5 47.6 140.9 51.8 37.8-10.2 80.9-15.6 129.1-15.6 48.5 0 91.8 5.6 129.8 15.9 12.9-9.8 77-55.8 138.8-50.2 3.3 8.8 28.2 66.7 6.3 135 37.1 42.1 56 94.6 56 151.4 0 117-67.5 215.3-228.8 243.7 26.9 26.6 43.6 63.4 43.6 104.2l0 128.8c0.9 10.3 0 20.5 17.2 20.5C878.1 942.4 1024 750.9 1024 525.3c0-282.9-229.3-512-512-512C229.1 13.2 0 242.3 0 525.2L0 525.2z" />
|
||
</svg>
|
||
</div>
|
||
</a>
|
||
</li></ul>
|
||
</div>
|
||
</div>
|
||
</div><div class="site-info mt-2">
|
||
<div>© Stop Talking, Start Doing 2021,
|
||
Powered by <a title="Jekyll is a simple, blog-aware, static site generator." href="http://jekyllrb.com/">Jekyll</a> & <a
|
||
title="TeXt is a super customizable Jekyll theme." href="https://github.com/kitian616/jekyll-TeXt-theme">TeXt Theme</a>.
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</footer>
|
||
</div></div>
|
||
</div><script>(function() {
|
||
var SOURCES = window.TEXT_VARIABLES.sources;
|
||
window.Lazyload.js(SOURCES.jquery, function() {
|
||
var $body = $('body'), $window = $(window);
|
||
var $pageRoot = $('.js-page-root'), $pageMain = $('.js-page-main');
|
||
var activeCount = 0;
|
||
function modal(options) {
|
||
var $root = this, visible, onChange, hideWhenWindowScroll = false;
|
||
var scrollTop;
|
||
function setOptions(options) {
|
||
var _options = options || {};
|
||
visible = _options.initialVisible === undefined ? false : show;
|
||
onChange = _options.onChange;
|
||
hideWhenWindowScroll = _options.hideWhenWindowScroll;
|
||
}
|
||
function init() {
|
||
setState(visible);
|
||
}
|
||
function setState(isShow) {
|
||
if (isShow === visible) {
|
||
return;
|
||
}
|
||
visible = isShow;
|
||
if (visible) {
|
||
activeCount++;
|
||
scrollTop = $(window).scrollTop() || $pageMain.scrollTop();
|
||
$root.addClass('modal--show');
|
||
$pageMain.scrollTop(scrollTop);
|
||
activeCount === 1 && ($pageRoot.addClass('show-modal'), $body.addClass('of-hidden'));
|
||
hideWhenWindowScroll && window.hasEvent('touchstart') && $window.on('scroll', hide);
|
||
$window.on('keyup', handleKeyup);
|
||
} else {
|
||
activeCount > 0 && activeCount--;
|
||
$root.removeClass('modal--show');
|
||
$window.scrollTop(scrollTop);
|
||
activeCount === 0 && ($pageRoot.removeClass('show-modal'), $body.removeClass('of-hidden'));
|
||
hideWhenWindowScroll && window.hasEvent('touchstart') && $window.off('scroll', hide);
|
||
$window.off('keyup', handleKeyup);
|
||
}
|
||
onChange && onChange(visible);
|
||
}
|
||
function show() {
|
||
setState(true);
|
||
}
|
||
function hide() {
|
||
setState(false);
|
||
}
|
||
function handleKeyup(e) {
|
||
// Char Code: 27 ESC
|
||
if (e.which === 27) {
|
||
hide();
|
||
}
|
||
}
|
||
setOptions(options);
|
||
init();
|
||
return {
|
||
show: show,
|
||
hide: hide,
|
||
$el: $root
|
||
};
|
||
}
|
||
$.fn.modal = modal;
|
||
});
|
||
})();
|
||
</script><div class="modal modal--overflow page__search-modal d-print-none js-page-search-modal"><script>
|
||
(function () {
|
||
var SOURCES = window.TEXT_VARIABLES.sources;
|
||
window.Lazyload.js(SOURCES.jquery, function() {
|
||
// search panel
|
||
var search = (window.search || (window.search = {}));
|
||
var useDefaultSearchBox = window.useDefaultSearchBox === undefined ?
|
||
true : window.useDefaultSearchBox ;
|
||
|
||
var $searchModal = $('.js-page-search-modal');
|
||
var $searchToggle = $('.js-search-toggle');
|
||
var searchModal = $searchModal.modal({ onChange: handleModalChange, hideWhenWindowScroll: true });
|
||
var modalVisible = false;
|
||
search.searchModal = searchModal;
|
||
|
||
var $searchBox = null;
|
||
var $searchInput = null;
|
||
var $searchClear = null;
|
||
|
||
function getModalVisible() {
|
||
return modalVisible;
|
||
}
|
||
search.getModalVisible = getModalVisible;
|
||
|
||
function handleModalChange(visible) {
|
||
modalVisible = visible;
|
||
if (visible) {
|
||
search.onShow && search.onShow();
|
||
useDefaultSearchBox && $searchInput[0] && $searchInput[0].focus();
|
||
} else {
|
||
search.onShow && search.onHide();
|
||
useDefaultSearchBox && $searchInput[0] && $searchInput[0].blur();
|
||
setTimeout(function() {
|
||
useDefaultSearchBox && ($searchInput.val(''), $searchBox.removeClass('not-empty'));
|
||
search.clear && search.clear();
|
||
window.pageAsideAffix && window.pageAsideAffix.refresh();
|
||
}, 400);
|
||
}
|
||
}
|
||
|
||
$searchToggle.on('click', function() {
|
||
modalVisible ? searchModal.hide() : searchModal.show();
|
||
});
|
||
// Char Code: 83 S, 191 /
|
||
$(window).on('keyup', function(e) {
|
||
if (!modalVisible && !window.isFormElement(e.target || e.srcElement) && (e.which === 83 || e.which === 191)) {
|
||
modalVisible || searchModal.show();
|
||
}
|
||
});
|
||
|
||
if (useDefaultSearchBox) {
|
||
$searchBox = $('.js-search-box');
|
||
$searchInput = $searchBox.children('input');
|
||
$searchClear = $searchBox.children('.js-icon-clear');
|
||
search.getSearchInput = function() {
|
||
return $searchInput.get(0);
|
||
};
|
||
search.getVal = function() {
|
||
return $searchInput.val();
|
||
};
|
||
search.setVal = function(val) {
|
||
$searchInput.val(val);
|
||
};
|
||
|
||
$searchInput.on('focus', function() {
|
||
$(this).addClass('focus');
|
||
});
|
||
$searchInput.on('blur', function() {
|
||
$(this).removeClass('focus');
|
||
});
|
||
$searchInput.on('input', window.throttle(function() {
|
||
var val = $(this).val();
|
||
if (val === '' || typeof val !== 'string') {
|
||
search.clear && search.clear();
|
||
} else {
|
||
$searchBox.addClass('not-empty');
|
||
search.onInputNotEmpty && search.onInputNotEmpty(val);
|
||
}
|
||
}, 400));
|
||
$searchClear.on('click', function() {
|
||
$searchInput.val(''); $searchBox.removeClass('not-empty');
|
||
search.clear && search.clear();
|
||
});
|
||
}
|
||
});
|
||
})();
|
||
</script><div class="search search--dark">
|
||
<div class="main">
|
||
<div class="search__header">Search</div>
|
||
<div class="search-bar">
|
||
<div class="search-box js-search-box">
|
||
<div class="search-box__icon-search"><i class="fas fa-search"></i></div>
|
||
<input type="text" />
|
||
<div class="search-box__icon-clear js-icon-clear">
|
||
<a><i class="fas fa-times"></i></a>
|
||
</div>
|
||
</div>
|
||
<button class="button button--theme-dark button--pill search__cancel js-search-toggle">
|
||
Cancel</button>
|
||
</div>
|
||
<div class="search-result js-search-result"></div>
|
||
</div>
|
||
</div>
|
||
<script>var SOURCES = window.TEXT_VARIABLES.sources;
|
||
var PAHTS = window.TEXT_VARIABLES.paths;
|
||
window.Lazyload.js([SOURCES.jquery, PAHTS.search_js], function() {
|
||
var search = (window.search || (window.search = {}));
|
||
var searchData = window.TEXT_SEARCH_DATA || {};
|
||
|
||
function memorize(f) {
|
||
var cache = {};
|
||
return function () {
|
||
var key = Array.prototype.join.call(arguments, ',');
|
||
if (key in cache) return cache[key];
|
||
else return cache[key] = f.apply(this, arguments);
|
||
};
|
||
}
|
||
|
||
/// search
|
||
function searchByQuery(query) {
|
||
var i, j, key, keys, cur, _title, result = {};
|
||
keys = Object.keys(searchData);
|
||
for (i = 0; i < keys.length; i++) {
|
||
key = keys[i];
|
||
for (j = 0; j < searchData[key].length; j++) {
|
||
cur = searchData[key][j], _title = cur.title;
|
||
if ((result[key] === undefined || result[key] && result[key].length < 4 )
|
||
&& _title.toLowerCase().indexOf(query.toLowerCase()) >= 0) {
|
||
if (result[key] === undefined) {
|
||
result[key] = [];
|
||
}
|
||
result[key].push(cur);
|
||
}
|
||
}
|
||
}
|
||
return result;
|
||
}
|
||
|
||
var renderHeader = memorize(function(header) {
|
||
return $('<p class="search-result__header">' + header + '</p>');
|
||
});
|
||
|
||
var renderItem = function(index, title, url) {
|
||
return $('<li class="search-result__item" data-index="' + index + '"><a class="button" href="' + url + '">' + title + '</a></li>');
|
||
};
|
||
|
||
function render(data) {
|
||
if (!data) { return null; }
|
||
var $root = $('<ul></ul>'), i, j, key, keys, cur, itemIndex = 0;
|
||
keys = Object.keys(data);
|
||
for (i = 0; i < keys.length; i++) {
|
||
key = keys[i];
|
||
$root.append(renderHeader(key));
|
||
for (j = 0; j < data[key].length; j++) {
|
||
cur = data[key][j];
|
||
$root.append(renderItem(itemIndex++, cur.title, cur.url));
|
||
}
|
||
}
|
||
return $root;
|
||
}
|
||
|
||
// search box
|
||
var $result = $('.js-search-result'), $resultItems;
|
||
var lastActiveIndex, activeIndex;
|
||
|
||
function clear() {
|
||
$result.html(null);
|
||
$resultItems = $('.search-result__item'); activeIndex = 0;
|
||
}
|
||
function onInputNotEmpty(val) {
|
||
$result.html(render(searchByQuery(val)));
|
||
$resultItems = $('.search-result__item'); activeIndex = 0;
|
||
$resultItems.eq(0).addClass('active');
|
||
}
|
||
|
||
search.clear = clear;
|
||
search.onInputNotEmpty = onInputNotEmpty;
|
||
|
||
function updateResultItems() {
|
||
lastActiveIndex >= 0 && $resultItems.eq(lastActiveIndex).removeClass('active');
|
||
activeIndex >= 0 && $resultItems.eq(activeIndex).addClass('active');
|
||
}
|
||
|
||
function moveActiveIndex(direction) {
|
||
var itemsCount = $resultItems ? $resultItems.length : 0;
|
||
if (itemsCount > 1) {
|
||
lastActiveIndex = activeIndex;
|
||
if (direction === 'up') {
|
||
activeIndex = (activeIndex - 1 + itemsCount) % itemsCount;
|
||
} else if (direction === 'down') {
|
||
activeIndex = (activeIndex + 1 + itemsCount) % itemsCount;
|
||
}
|
||
updateResultItems();
|
||
}
|
||
}
|
||
|
||
// Char Code: 13 Enter, 37 ⬅, 38 ⬆, 39 ➡, 40 ⬇
|
||
$(window).on('keyup', function(e) {
|
||
var modalVisible = search.getModalVisible && search.getModalVisible();
|
||
if (modalVisible) {
|
||
if (e.which === 38) {
|
||
modalVisible && moveActiveIndex('up');
|
||
} else if (e.which === 40) {
|
||
modalVisible && moveActiveIndex('down');
|
||
} else if (e.which === 13) {
|
||
modalVisible && $resultItems && activeIndex >= 0 && $resultItems.eq(activeIndex).children('a')[0].click();
|
||
}
|
||
}
|
||
});
|
||
|
||
$result.on('mouseover', '.search-result__item > a', function() {
|
||
var itemIndex = $(this).parent().data('index');
|
||
itemIndex >= 0 && (lastActiveIndex = activeIndex, activeIndex = itemIndex, updateResultItems());
|
||
});
|
||
});
|
||
</script>
|
||
</div></div>
|
||
|
||
|
||
<script>(function() {
|
||
var SOURCES = window.TEXT_VARIABLES.sources;
|
||
window.Lazyload.js(SOURCES.jquery, function() {
|
||
function scrollToAnchor(anchor, duration, callback) {
|
||
var $root = this;
|
||
$root.animate({ scrollTop: $(anchor).position().top }, duration, function() {
|
||
window.history.replaceState(null, '', window.location.href.split('#')[0] + anchor);
|
||
callback && callback();
|
||
});
|
||
}
|
||
$.fn.scrollToAnchor = scrollToAnchor;
|
||
});
|
||
})();
|
||
(function() {
|
||
var SOURCES = window.TEXT_VARIABLES.sources;
|
||
window.Lazyload.js(SOURCES.jquery, function() {
|
||
function affix(options) {
|
||
var $root = this, $window = $(window), $scrollTarget, $scroll,
|
||
offsetBottom = 0, scrollTarget = window, scroll = window.document, disabled = false, isOverallScroller = true,
|
||
rootTop, rootLeft, rootHeight, scrollBottom, rootBottomTop,
|
||
hasInit = false, curState;
|
||
|
||
function setOptions(options) {
|
||
var _options = options || {};
|
||
_options.offsetBottom && (offsetBottom = _options.offsetBottom);
|
||
_options.scrollTarget && (scrollTarget = _options.scrollTarget);
|
||
_options.scroll && (scroll = _options.scroll);
|
||
_options.disabled !== undefined && (disabled = _options.disabled);
|
||
$scrollTarget = $(scrollTarget);
|
||
isOverallScroller = window.isOverallScroller($scrollTarget[0]);
|
||
$scroll = $(scroll);
|
||
}
|
||
function preCalc() {
|
||
top();
|
||
rootHeight = $root.outerHeight();
|
||
rootTop = $root.offset().top + (isOverallScroller ? 0 : $scrollTarget.scrollTop());
|
||
rootLeft = $root.offset().left;
|
||
}
|
||
function calc(needPreCalc) {
|
||
needPreCalc && preCalc();
|
||
scrollBottom = $scroll.outerHeight() - offsetBottom - rootHeight;
|
||
rootBottomTop = scrollBottom - rootTop;
|
||
}
|
||
function top() {
|
||
if (curState !== 'top') {
|
||
$root.removeClass('fixed').css({
|
||
left: 0,
|
||
top: 0
|
||
});
|
||
curState = 'top';
|
||
}
|
||
}
|
||
function fixed() {
|
||
if (curState !== 'fixed') {
|
||
$root.addClass('fixed').css({
|
||
left: rootLeft + 'px',
|
||
top: 0
|
||
});
|
||
curState = 'fixed';
|
||
}
|
||
}
|
||
function bottom() {
|
||
if (curState !== 'bottom') {
|
||
$root.removeClass('fixed').css({
|
||
left: 0,
|
||
top: rootBottomTop + 'px'
|
||
});
|
||
curState = 'bottom';
|
||
}
|
||
}
|
||
function setState() {
|
||
var scrollTop = $scrollTarget.scrollTop();
|
||
if (scrollTop >= rootTop && scrollTop <= scrollBottom) {
|
||
fixed();
|
||
} else if (scrollTop < rootTop) {
|
||
top();
|
||
} else {
|
||
bottom();
|
||
}
|
||
}
|
||
function init() {
|
||
if(!hasInit) {
|
||
var interval, timeout;
|
||
calc(true); setState();
|
||
// run calc every 100 millisecond
|
||
interval = setInterval(function() {
|
||
calc();
|
||
}, 100);
|
||
timeout = setTimeout(function() {
|
||
clearInterval(interval);
|
||
}, 45000);
|
||
window.pageLoad.then(function() {
|
||
setTimeout(function() {
|
||
clearInterval(interval);
|
||
clearTimeout(timeout);
|
||
}, 3000);
|
||
});
|
||
$scrollTarget.on('scroll', function() {
|
||
disabled || setState();
|
||
});
|
||
$window.on('resize', function() {
|
||
disabled || (calc(true), setState());
|
||
});
|
||
hasInit = true;
|
||
}
|
||
}
|
||
|
||
setOptions(options);
|
||
if (!disabled) {
|
||
init();
|
||
}
|
||
$window.on('resize', window.throttle(function() {
|
||
init();
|
||
}, 200));
|
||
return {
|
||
setOptions: setOptions,
|
||
refresh: function() {
|
||
calc(true, { animation: false }); setState();
|
||
}
|
||
};
|
||
}
|
||
$.fn.affix = affix;
|
||
});
|
||
})();
|
||
(function() {
|
||
var SOURCES = window.TEXT_VARIABLES.sources;
|
||
window.Lazyload.js(SOURCES.jquery, function() {
|
||
function toc(options) {
|
||
var $root = this, $window = $(window), $scrollTarget, $scroller, $tocUl = $('<ul class="toc toc--ellipsis"></ul>'), $tocLi, $headings, $activeLast, $activeCur,
|
||
selectors = 'h1,h2,h3', container = 'body', scrollTarget = window, scroller = 'html, body', disabled = false,
|
||
headingsPos, scrolling = false, hasRendered = false, hasInit = false;
|
||
|
||
function setOptions(options) {
|
||
var _options = options || {};
|
||
_options.selectors && (selectors = _options.selectors);
|
||
_options.container && (container = _options.container);
|
||
_options.scrollTarget && (scrollTarget = _options.scrollTarget);
|
||
_options.scroller && (scroller = _options.scroller);
|
||
_options.disabled !== undefined && (disabled = _options.disabled);
|
||
$headings = $(container).find(selectors).filter('[id]');
|
||
$scrollTarget = $(scrollTarget);
|
||
$scroller = $(scroller);
|
||
}
|
||
function calc() {
|
||
headingsPos = [];
|
||
$headings.each(function() {
|
||
headingsPos.push(Math.floor($(this).position().top));
|
||
});
|
||
}
|
||
function setState(element, disabled) {
|
||
var scrollTop = $scrollTarget.scrollTop(), i;
|
||
if (disabled || !headingsPos || headingsPos.length < 1) { return; }
|
||
if (element) {
|
||
$activeCur = element;
|
||
} else {
|
||
for (i = 0; i < headingsPos.length; i++) {
|
||
if (scrollTop >= headingsPos[i]) {
|
||
$activeCur = $tocLi.eq(i);
|
||
} else {
|
||
$activeCur || ($activeCur = $tocLi.eq(i));
|
||
break;
|
||
}
|
||
}
|
||
}
|
||
$activeLast && $activeLast.removeClass('active');
|
||
($activeLast = $activeCur).addClass('active');
|
||
}
|
||
function render() {
|
||
if(!hasRendered) {
|
||
$root.append($tocUl);
|
||
$headings.each(function() {
|
||
var $this = $(this);
|
||
$tocUl.append($('<li></li>').addClass('toc-' + $this.prop('tagName').toLowerCase())
|
||
.append($('<a></a>').text($this.text()).attr('href', '#' + $this.prop('id'))));
|
||
});
|
||
$tocLi = $tocUl.children('li');
|
||
$tocUl.on('click', 'a', function(e) {
|
||
e.preventDefault();
|
||
var $this = $(this);
|
||
scrolling = true;
|
||
setState($this.parent());
|
||
$scroller.scrollToAnchor($this.attr('href'), 400, function() {
|
||
scrolling = false;
|
||
});
|
||
});
|
||
}
|
||
hasRendered = true;
|
||
}
|
||
function init() {
|
||
var interval, timeout;
|
||
if(!hasInit) {
|
||
render(); calc(); setState(null, scrolling);
|
||
// run calc every 100 millisecond
|
||
interval = setInterval(function() {
|
||
calc();
|
||
}, 100);
|
||
timeout = setTimeout(function() {
|
||
clearInterval(interval);
|
||
}, 45000);
|
||
window.pageLoad.then(function() {
|
||
setTimeout(function() {
|
||
clearInterval(interval);
|
||
clearTimeout(timeout);
|
||
}, 3000);
|
||
});
|
||
$scrollTarget.on('scroll', function() {
|
||
disabled || setState(null, scrolling);
|
||
});
|
||
$window.on('resize', window.throttle(function() {
|
||
if (!disabled) {
|
||
render(); calc(); setState(null, scrolling);
|
||
}
|
||
}, 100));
|
||
}
|
||
hasInit = true;
|
||
}
|
||
|
||
setOptions(options);
|
||
if (!disabled) {
|
||
init();
|
||
}
|
||
$window.on('resize', window.throttle(function() {
|
||
init();
|
||
}, 200));
|
||
return {
|
||
setOptions: setOptions
|
||
};
|
||
}
|
||
$.fn.toc = toc;
|
||
});
|
||
})();
|
||
/*(function () {
|
||
|
||
})();*/
|
||
</script><script>
|
||
/* toc must before affix, since affix need to konw toc' height. */(function() {
|
||
var SOURCES = window.TEXT_VARIABLES.sources;
|
||
var TOC_SELECTOR = window.TEXT_VARIABLES.site.toc.selectors;
|
||
window.Lazyload.js(SOURCES.jquery, function() {
|
||
var $window = $(window);
|
||
var $articleContent = $('.js-article-content');
|
||
var $tocRoot = $('.js-toc-root'), $col2 = $('.js-col-aside');
|
||
var toc;
|
||
var tocDisabled = false;
|
||
var hasSidebar = $('.js-page-root').hasClass('layout--page--sidebar');
|
||
var hasToc = $articleContent.find(TOC_SELECTOR).length > 0;
|
||
|
||
function disabled() {
|
||
return $col2.css('display') === 'none' || !hasToc;
|
||
}
|
||
|
||
tocDisabled = disabled();
|
||
|
||
toc = $tocRoot.toc({
|
||
selectors: TOC_SELECTOR,
|
||
container: $articleContent,
|
||
scrollTarget: hasSidebar ? '.js-page-main' : null,
|
||
scroller: hasSidebar ? '.js-page-main' : null,
|
||
disabled: tocDisabled
|
||
});
|
||
|
||
$window.on('resize', window.throttle(function() {
|
||
tocDisabled = disabled();
|
||
toc && toc.setOptions({
|
||
disabled: tocDisabled
|
||
});
|
||
}, 100));
|
||
|
||
});
|
||
})();
|
||
(function() {
|
||
var SOURCES = window.TEXT_VARIABLES.sources;
|
||
window.Lazyload.js(SOURCES.jquery, function() {
|
||
var $window = $(window), $pageFooter = $('.js-page-footer');
|
||
var $pageAside = $('.js-page-aside');
|
||
var affix;
|
||
var tocDisabled = false;
|
||
var hasSidebar = $('.js-page-root').hasClass('layout--page--sidebar');
|
||
|
||
affix = $pageAside.affix({
|
||
offsetBottom: $pageFooter.outerHeight(),
|
||
scrollTarget: hasSidebar ? '.js-page-main' : null,
|
||
scroller: hasSidebar ? '.js-page-main' : null,
|
||
scroll: hasSidebar ? $('.js-page-main').children() : null,
|
||
disabled: tocDisabled
|
||
});
|
||
|
||
$window.on('resize', window.throttle(function() {
|
||
affix && affix.setOptions({
|
||
disabled: tocDisabled
|
||
});
|
||
}, 100));
|
||
|
||
window.pageAsideAffix = affix;
|
||
});
|
||
})();
|
||
</script>
|
||
</div>
|
||
<script>(function () {
|
||
var $root = document.getElementsByClassName('root')[0];
|
||
if (window.hasEvent('touchstart')) {
|
||
$root.dataset.isTouch = true;
|
||
document.addEventListener('touchstart', function(){}, false);
|
||
}
|
||
})();
|
||
</script>
|
||
</body>
|
||
</html>
|
||
|