吳恩緯
    • Create new note
    • Create a note from template
      • Sharing URL Link copied
      • /edit
      • View mode
        • Edit mode
        • View mode
        • Book mode
        • Slide mode
        Edit mode View mode Book mode Slide mode
      • Customize slides
      • Note Permission
      • Read
        • Only me
        • Signed-in users
        • Everyone
        Only me Signed-in users Everyone
      • Write
        • Only me
        • Signed-in users
        • Everyone
        Only me Signed-in users Everyone
      • Engagement control Commenting, Suggest edit, Emoji Reply
    • Invite by email
      Invitee

      This note has no invitees

    • Publish Note

      Share your work with the world Congratulations! 🎉 Your note is out in the world Publish Note

      Your note will be visible on your profile and discoverable by anyone.
      Your note is now live.
      This note is visible on your profile and discoverable online.
      Everyone on the web can find and read all notes of this public team.
      See published notes
      Unpublish note
      Please check the box to agree to the Community Guidelines.
      View profile
    • Commenting
      Permission
      Disabled Forbidden Owners Signed-in users Everyone
    • Enable
    • Permission
      • Forbidden
      • Owners
      • Signed-in users
      • Everyone
    • Suggest edit
      Permission
      Disabled Forbidden Owners Signed-in users Everyone
    • Enable
    • Permission
      • Forbidden
      • Owners
      • Signed-in users
    • Emoji Reply
    • Enable
    • Versions and GitHub Sync
    • Note settings
    • Note Insights
    • Engagement control
    • Transfer ownership
    • Delete this note
    • Save as template
    • Insert from template
    • Import from
      • Dropbox
      • Google Drive
      • Gist
      • Clipboard
    • Export to
      • Dropbox
      • Google Drive
      • Gist
    • Download
      • Markdown
      • HTML
      • Raw HTML
Menu Note settings Versions and GitHub Sync Note Insights Sharing URL Create Help
Create Create new note Create a note from template
Menu
Options
Engagement control Transfer ownership Delete this note
Import from
Dropbox Google Drive Gist Clipboard
Export to
Dropbox Google Drive Gist
Download
Markdown HTML Raw HTML
Back
Sharing URL Link copied
/edit
View mode
  • Edit mode
  • View mode
  • Book mode
  • Slide mode
Edit mode View mode Book mode Slide mode
Customize slides
Note Permission
Read
Only me
  • Only me
  • Signed-in users
  • Everyone
Only me Signed-in users Everyone
Write
Only me
  • Only me
  • Signed-in users
  • Everyone
Only me Signed-in users Everyone
Engagement control Commenting, Suggest edit, Emoji Reply
  • Invite by email
    Invitee

    This note has no invitees

  • Publish Note

    Share your work with the world Congratulations! 🎉 Your note is out in the world Publish Note

    Your note will be visible on your profile and discoverable by anyone.
    Your note is now live.
    This note is visible on your profile and discoverable online.
    Everyone on the web can find and read all notes of this public team.
    See published notes
    Unpublish note
    Please check the box to agree to the Community Guidelines.
    View profile
    Engagement control
    Commenting
    Permission
    Disabled Forbidden Owners Signed-in users Everyone
    Enable
    Permission
    • Forbidden
    • Owners
    • Signed-in users
    • Everyone
    Suggest edit
    Permission
    Disabled Forbidden Owners Signed-in users Everyone
    Enable
    Permission
    • Forbidden
    • Owners
    • Signed-in users
    Emoji Reply
    Enable
    Import from Dropbox Google Drive Gist Clipboard
       owned this note    owned this note      
    Published Linked with GitHub
    7
    Subscribed
    • Any changes
      Be notified of any changes
    • Mention me
      Be notified of mention me
    • Unsubscribe
    Subscribe
    # Linux 核心網路:第一章 Above protocol stack (socket layer) > 作者:吳恩緯 (enweiwu@FreeBSD.org) :::warning :information_source: 聲明 1. 這文是探討 Linux 核心網路子系統的系列文章之一,之後陸續會有多個章節探討 socket layer 以下的協定層。 2. 勘誤與討論,請在 Facebook「[系統軟體系列課程](https://www.facebook.com/groups/system.software2023/)」討論。 3. 本文探討 Linux 核心網路子系統,而非應用程式的網路程式設計。 ::: ## BSD socket [Berkeley socket](https://en.wikipedia.org/wiki/Berkeley_sockets) 源於 4.2BSD,是核心開放給使用者層級的程式開發介面 (API),允許後者藉由 socket 間接存取到核心的網路及行程間通訊的協定。後來 POSIX 將 Berkeley socket 予以標準化,部分程式開發介面已變更,Linux 核心和應用程式主要採納 POSIX 規範的 socket 介面,但也有 BSD 實作的身影。 依據 Dictionary.com,[socket](https://www.dictionary.com/browse/socket) 一詞的解釋如下: > a hollow part or piece for receiving and holding some part or thing. 漢語的「插座」是 [socket](https://www.dictionary.com/browse/socket) 一詞在電氣領域的特化用語,但不代表 socket 就只翻譯為「插座」 —— socket 原本在英語就有多個意思,例如 [eye socket](https://en.wikipedia.org/wiki/Orbit_(anatomy)) 指眼眶,後者是顱骨的一個體腔,眼球就位於眼眶中。無論是解剖學還是在電器領域,socket 都有連接後,得以存取某種資源和支撐某個部分的寓意。由於「插座」在漢語已是特化用語,我們就不以「插座」來稱呼電腦網路領域的 socket。 要注意的是, socket API 支援的底層網路及行程間通訊協定不僅限於 TCP/IP, 還有諸多如 Unix domain socket, netlink 等等。詳細的協定總類請參閱 [socket(2)](https://man7.org/linux/man-pages/man2/socket.2.html)。 我們常常聽到開發者會說「產生一個 socket」或「一個 TCP socket」。相信很多讀者會有困惑,socket 不是程式開發介面 (API) 嗎, 怎麼會用量詞「一個」呢?是的,socket 其實還隱含另一個意思,也就是使用者呼叫 socket API 的 `socket()` 後得到的 file descriptor,我們把它稱之為 endpoint。`socket()`的函式宣告如下: ```c #include <sys/socket.h> int socket(int domain, int type, int protocol); ``` 此函式的參數有: 1. `domain`: 使用的核心網路層,與 address family 以及 protocol family 為同義詞。在標頭檔中定義為 `AF_*` 或 `PF_*`。常見的有: - `AF_INET`: TCP/IP,網路層為 IPv4。 - `AF_INET6`: TCP/IP,網路層為 IPv6。 - `AF_UNIX`: Unix domain socket。 - `AF_NETLINK`: netlink socket, 用於 user space 以及 kernel space 之間的通訊 2. `type`: socket 的形式,其根據指定的 `domain` 而有不同的語意。常見的有: - `SOCK_STREAM`: 提供 connection-oriented, reliable 的 byte streams。 - `SOCK_DGRAM`: connectionless, unreliable 的通道。 - `SOCK_RAW`: bypass 核心網路層,於用戶空間實作網路層。 3. `protocol`: 指定網路協定。大多數的情況`domain` 及 `type` 給定後核心的 socket layer 便能決定特定的網路協定, 故通常填 `0`。 通常使用 socket API 的第一步就是呼叫 `socket()`, 回傳值為核心分配的 file descriptor (以下稱 `fd`), 在呼叫其他 socket API 或相關函式時將 `fd` 作為參數傳入。又因為一個 socket 在使用者的視角即是個 file descriptor, 因此可以作為其他帶有 file descriptor 參數的系統呼叫的參數,如 `read()`, `write()`, `ioctl()`, `fcntl()`, `select()` 等等。 Socket 介面提供了一系列的程式開發介面 (API) 給使用者, 常見的有:`socket()`, `bind()`, `listen()`, `accept()`, `send()`, `sendto()`, `recv()`, `recvfrom()`, `getsockopt()`, `setsockopt()` 等等。 這些 socket API 大多數為系統呼叫,核心的系統呼叫層有對應的函示負責處理,如 `socket()` 在系統呼叫層的名稱為 `sys_socket()` (由 `SYSCALL_DEFINE3` 巨集展開): ```c SYSCALL_DEFINE3(socket, int, family, int, type, int, protocol) { return __sys_socket(family, type, protocol); } ``` 相關函式將在後續做詳細探討。 以下章節主要探討的是 Linux 核心層級網路子系統的實作, 而非使用者層級的 socket 程式設計。若對 socket 程式設計不熟悉, 建議先去閱讀 [Beej's Guide to Network Programming. Using Internet Sockets](https://www.beej.us/guide/bgnet/) 或者相關材料。《[UNIX Network Programming](https://en.wikipedia.org/wiki/UNIX_Network_Programming)》是這個領域的經典書籍。 ## Layering 下圖展示 socket layer 作為 socket API 以及核心網路層之間的橋樑: ![](https://hackmd.io/_uploads/HJbgBiWr3.png) ### Socket address 為了與達成與目標行程或主機之間的通訊,一個 socket endpoint 需要指定一個 socket address。不同的 address family 的 socket address 所隱含的意義及成員不同,例如 `AF_INET` 的 socket address `struct sockaddr_in` 包含了 IP address 以及 port number,而 `AF_NETLINK` 的 socket address `struct sockaddr_nl` 則包含 pid 以及 multicast groups mask (本章節只討論 TCP/IP 核心網路層,若對 netlink 有興趣可以參閱 [Linux kernel document: Introduction to Netlink](https://www.kernel.org/doc/html/latest/userspace-api/netlink/intro.html))。 既然存在好幾種 socket address,則為了統一傳入 socket API 函式的參數,需要一個結構體來表示所有的 socket address,這個結構體為 `struct sockaddr`: ```c struct sockaddr { unsigned short sa_family; /* address family, AF_xxx */ char sa_data[14]; /* 14 bytes of protocol address */ }; ``` 並觀察 `struct sockaddr_in` 的定義: ```c struct sockaddr_in { unsigned short sin_family; /* Address family */ __be16 sin_port; /* Port number */ struct in_addr sin_addr; /* Internet address */ unsigned char __pad[8]; /* Pad to size of `struct sockaddr'. */ }; ``` ```c struct in_addr { __be32 s_addr; }; ``` 應用程式通常在操作完特定的 socket address 後會將其強制轉型為 `sockaddr *`,作為 socket API 的參數傳入: ```c struct sockaddr_in serv; /* Operate on struct sockaddr_in * ... */ sendto(fd, buf, BUFSIZE, 0, (struct sockaddr *) &serv, sizeof(serv)); ``` 我們可以以物件導向的角度思考轉型這件事,則 `struct sockaddr` 即為父類別,`struct sockaddr_in` 以及其他種 socket address 即為子類別。物件導向的概念在大量出現在網路程式以及 Linux 核心,並且是以「C 語言」實現,因為這邊說的物件導向是「概念」而不是「語法」,可參照〈[你所不知道的 C 語言:物件導向程式設計篇](https://hackmd.io/@sysprog/c-oop)〉。 回到 `struct sockaddr_in`, 我們可以發現兩件事: 1. `sin_port` 以及 `s_addr` 的型別分別為 `__be16` 以及 `__be32`,這是因為 network order 為 big endian。`__be16` 及 `__be32` 的定義在 [include/uapi/linux/types.h](https://elixir.bootlin.com/linux/latest/source/include/uapi/linux/types.h)。 2. `struct sockaddr` 的大小 (16 bytes) 足以容納 `struct sockaddr_in`。 讓我們再看看 IPv6 (`AF_INET6`) 的 socket address `struct sockaddr_in6`: ```c struct sockaddr_in6 { unsigned short int sin6_family; /* AF_INET6 */ __be16 sin6_port; /* Transport layer port # */ __be32 sin6_flowinfo; /* IPv6 flow information */ struct in6_addr sin6_addr; /* IPv6 address */ __u32 sin6_scope_id; /* scope id (new in RFC2553) */ }; ``` ```c struct in6_addr { union { uint8_t __u6_addr8[16]; uint16_t __u6_addr16[8]; uint32_t __u6_addr32[4]; } __in6_u; }; ``` 我們可以發現 `struct sockaddr_in6` 的大小是 28 bytes,遠大於 `struct sockaddr` 的 16 bytes。 實際上 `struct sockaddr` 是個錯誤,因為該結構體的空間不足以容納多數的 socket address。於是更大的結構體被提出: ```c /* 128 bytes */ struct sockaddr_storage { u16 ss_family; char __data[126]; } ``` 但可惜的是,由於歷史因素,許多舊的程式以及 socket API 仍然使用 `struct sockaddr`。 ## Linux 核心中的 socket socket 的表示在 Linux 核心中分為以下二個結構體: - `struct socket`: BSD socket,銜接使用者層面的抽象結構,較為靠近使用者空間。 - `struct sock`: INET socket,保存網路連線的資訊 INET 與 TCP/IP 為同義詞,可見 `struct sock` 與核心網路層更為靠近。 讀到這邊讀者一定會分不清楚這兩個結構體到底差在哪裡, 讓我們細部解釋他們: ## struct socket 結構體 `struct socket` 代表一個 BSD socket,其提供的操作通常與系統呼叫有關 (通常一個系統呼叫完會緊接著此結構體的操作)。以下會做更詳細的說明。 ```c /** * struct socket - general BSD socket * @state: socket state (%SS_CONNECTED, etc) * @type: socket type (%SOCK_STREAM, etc) * @flags: socket flags (%SOCK_NOSPACE, etc) * @ops: protocol specific socket operations * @file: File back pointer for gc * @sk: internal networking protocol agnostic socket representation * @wq: wait queue for several uses */ struct socket { socket_state state; short type; unsigned long flags; struct socket_wq __rcu *wq; struct file *file; struct sock *sk; const struct proto_ops *ops; }; ``` 可以看到裡面內嵌指向 `struct sock` 的指標,事實上 `struct sock` 裡頭一樣內嵌指向 `struct socket` 的指標。並且,我們可以看到型別為 `struct file` 的成員,其為 `socket()` 回傳的 file descriptor 指向的 open file structure。 觀察成員 `ops`,其儲存了很多 protocol-specific (INET, netlink 等) 的操作: ```c struct proto_ops { int family; struct module *owner; int (*release) (struct socket *sock); int (*bind) (struct socket *sock, struct sockaddr *myaddr, int sockaddr_len); int (*connect) (struct socket *sock, struct sockaddr *vaddr, int sockaddr_len, int flags); int (*listen) (struct socket *sock, int len); struct socket *sock2); int (*accept) (struct socket *sock, struct socket *newsock, int flags, bool kern); int (*sendmsg) (struct socket *sock, struct msghdr *m, size_t total_len); int (*recvmsg) (struct socket *sock, struct msghdr *m, size_t total_len, int flags); #if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 8, 0) int (*setsockopt)(struct socket *sock, int level, int optname, sockptr_t optval, unsigned int optlen); int (*getsockopt)(struct socket *sock, int level, int optname, char __user *optval, int __user *optlen); #endif /* ... */ } ``` `struct proto_ops` 中的操作與核心提供的 socket API 名稱非常相似。以下是 INET socket 對 `struct proto_ops` 的定義 ([net/ipv4/af_inet.c](https://elixir.bootlin.com/linux/latest/source/net/ipv4/af_inet.c)): ```c const struct proto_ops inet_stream_ops = { .family = PF_INET, .owner = THIS_MODULE, .release = inet_release, .bind = inet_bind, .connect = inet_stream_connect, .accept = inet_accept, .poll = tcp_poll, .ioctl = inet_ioctl, .listen = inet_listen, .setsockopt = sock_common_setsockopt, .getsockopt = sock_common_getsockopt, .sendmsg = inet_sendmsg, .recvmsg = inet_recvmsg, /* ... */ }; ``` 事實上,這些操作會緊跟隨與其同名的 socket API 後被呼叫,如 `bind()`: 1. 系統呼叫層 ```c SYSCALL_DEFINE3(bind, int, fd, struct sockaddr __user *, umyaddr, int, addrlen) { return __sys_bind(fd, umyaddr, addrlen); } ``` 2. socket layer ```c int __sys_bind(int fd, struct sockaddr __user *umyaddr, int addrlen) { struct socket *sock; struct sockaddr_storage address; int err, fput_needed; sock = sockfd_lookup_light(fd, &err, &fput_needed); if (sock) { /* ... */ err = sock->ops->bind(sock, (struct sockaddr *) &address, addrlen); } return err; } ``` 3. TCP/IP stack ```c int inet_bind(struct socket *sock, struct sockaddr *uaddr, int addr_len) { /* ... */ } ``` ## struct socket 相關操作 `struct socket` 的相關操作通常都是 `sock_` 開頭。 ### 建立 socket 建立 `struct socket` 通常會緊跟隨使用者層級呼叫 `socket()`。事實上,並非只有應用程式可建立 socket endpoint,核心層級也可以建立,[khttpd](https://github.com/sysprog21/khttpd) 是個很好的範例,其在核心層級實作一個 HTTP 伺服器。 根據 `struct socket` 被建立於用戶層級或核心層級,socket layer 提供不同的函式: - `int sock_create(int family, int type, int protocol, struct socket **res)`: 在使用者層級呼叫 `socket()` 後建立 `struct socket`。 - `int sock_create_kern(struct net *net, int family, int type, int protocol, struct socket **res)`: 產生一個核心 socket。 - `int sock_create_lite(int family, int type, int protocol, struct socket **res)`: 無邊界檢查版本的 `sock_create_kern()`。 以上函式建立的 `struct socket` 會指派到 `res`。`sock_create_kern` 的參數 `net` 指向一個 network namespace,通常設定為全域變數 `init_net`。 `sock_create()` 會進一步呼叫與 address family 關聯的 `create()` 函式: ```c err = pf->create(net, sock, protocol, kern); /* pf is specific to protocol family */ if (err < 0) goto out_module_put; ``` 以 `AF_INET` 為例,即 `inet_create()`。 ### 傳送/接收 messages 以下函式負責傳送或接收 messages: - `int sock_recvmsg(struct socket *sock, struct msghdr *msg, int flags);` - `int kernel_recvmsg(struct socket *sock, struct msghdr *msg, struct kvec *vec, size_t num, size_t size, int flags);` - `int sock_sendmsg(struct socket *sock, struct msghdr *msg);` - `int kernel_sendmsg(struct socket *sock, struct msghdr *msg, struct kvec *vec, size_t num, size_t size);` 上述函式中的以 `kernel_` 開頭的用於核心 socket。這些函式會進一步呼叫 `sock->ops->sendmsg()` 或 `sock->ops->recvmsg()`。 以上函式傳送/接收的資料是以 `struct msghdr` 的形式存在。在探討他之前,我們可以先用以下圖理解資料在不同層級時的存在形式: ![](https://hackmd.io/_uploads/BkaC1QQr2.png) 如上圖所示,每個層級對資料的封裝不同。圖中的 `struct sk_buff` 是核心網路子系統中極其重要的結構,於下一個章節會詳細探討。 回到 `struct msghdr`,其重要的欄位有兩個: - `void *msg_name;`: socket address, UDP socket 的必要欄位。 - `struct iov_iter msg_iter`: 包含 `struct iovec` 的陣列。以 `send()` 或 `read()` 為例,陣列長度為 1; 而在呼叫 scatter-gether IO 系統呼叫時 (諸如 `writev()` 或 `readv()`),陣列長度為參數 `iovcnt`。 `struct kvec` 的定義如下: ```c struct kvec { void *iov_base; /* and that should *never* hold a userland pointer */ size_t iov_len; }; ``` 與 `struct iovec` 的差別僅在於 `iov_base` 是核心空間的地址,而不是用戶空間的地址。 ## struct sock `struct sock` 代表一個 INET socket (`AF_INET`, `AF_INET6`)。其相較於 `struct socket` 更接近網路協定層。其他的協定如 `AF_UNIX` 有 `struct unix_sock`, `AF_NETLINK` 有 `struct netlink_sock`。 以下是 `struct sock` 的主要成員: ```c struct sock { unsigned int sk_padding : 1, sk_no_check_tx : 1, sk_no_check_rx : 1, sk_userlocks : 4, sk_protocol : 8, sk_type : 16; struct socket *sk_socket; struct sk_buff *sk_send_head; // ... void (*sk_state_change)(struct sock *sk); void (*sk_data_ready)(struct sock *sk); void (*sk_write_space)(struct sock *sk); void (*sk_error_report)(struct sock *sk); int (*sk_backlog_rcv)(struct sock *sk, struct sk_buff *skb); void (*sk_destruct)(struct sock *sk); }; ``` `sk_protocol` 為 socket 使用的協定,`sk_type` 為 socket type (`SOCK_STREAM`, `SOCK_DGRAM`, etc.)。 `sk_socket` 為指向 `struct socket` 的 back pointer。在 Linux 核心中,我們常常可以看到兩個結構體互相內嵌在彼此的結構體。 `sk_send_head` 是用以傳送封包的 linked list 的 head,其節點的型別為 `struct sk_buff`,表示網路封包以及內部資訊,在之後的章節會詳細探討。 `struct sock` 的初始化以及 attach 到 BSD socket 發生於 `inet_create()`: ```c static int inet_create(struct net *net, struct socket *sock, int protocol, int kern) { sk = sk_alloc(net, PF_INET, GFP_KERNEL, answer_prot, kern); // ... } ``` `struct sock` 相關操作的函式名稱以 `sk_` 開頭 (不同於 `struct socket` 的 `sock_`)。 ## 傳送/接收之系統呼叫 workflow 以下是使用者層級呼叫 `send()`/`sendto()`, `recv()`/`recvfrom()` 後進入核心, 呼叫的函式順序。這邊只列出使用者層級到協定層上端的的函式,協定層之下則待後續章節解說。 ![](https://i.imgur.com/N6CBWCF.png) ## 參考文獻 - TCP IP Illustrated Volume 2 The Implementation (Addison-Wesley 出版) - [Understanding Linux Network Internals](https://www.oreilly.com/library/view/understanding-linux-network/0596002556/) - [linux-kernel-labs-networking](https://linux-kernel-labs.github.io/refs/heads/master/labs/networking.html#sending-receiving-messages) - [linux-kernel-source](https://elixir.bootlin.com/linux/latest/source) - [linux-network-performance-parameters](https://github.com/leandromoreira/linux-network-performance-parameters)

    Import from clipboard

    Paste your markdown or webpage here...

    Advanced permission required

    Your current role can only read. Ask the system administrator to acquire write and comment permission.

    This team is disabled

    Sorry, this team is disabled. You can't edit this note.

    This note is locked

    Sorry, only owner can edit this note.

    Reach the limit

    Sorry, you've reached the max length this note can be.
    Please reduce the content or divide it to more notes, thank you!

    Import from Gist

    Import from Snippet

    or

    Export to Snippet

    Are you sure?

    Do you really want to delete this note?
    All users will lose their connection.

    Create a note from template

    Create a note from template

    Oops...
    This template has been removed or transferred.
    Upgrade
    All
    • All
    • Team
    No template.

    Create a template

    Upgrade

    Delete template

    Do you really want to delete this template?
    Turn this template into a regular note and keep its content, versions, and comments.

    This page need refresh

    You have an incompatible client version.
    Refresh to update.
    New version available!
    See releases notes here
    Refresh to enjoy new features.
    Your user state has changed.
    Refresh to load new user state.

    Sign in

    Forgot password

    or

    By clicking below, you agree to our terms of service.

    Sign in via Facebook Sign in via Twitter Sign in via GitHub Sign in via Dropbox Sign in with Wallet
    Wallet ( )
    Connect another wallet

    New to HackMD? Sign up

    Help

    • English
    • 中文
    • Français
    • Deutsch
    • 日本語
    • Español
    • Català
    • Ελληνικά
    • Português
    • italiano
    • Türkçe
    • Русский
    • Nederlands
    • hrvatski jezik
    • język polski
    • Українська
    • हिन्दी
    • svenska
    • Esperanto
    • dansk

    Documents

    Help & Tutorial

    How to use Book mode

    Slide Example

    API Docs

    Edit in VSCode

    Install browser extension

    Contacts

    Feedback

    Discord

    Send us email

    Resources

    Releases

    Pricing

    Blog

    Policy

    Terms

    Privacy

    Cheatsheet

    Syntax Example Reference
    # Header Header 基本排版
    - Unordered List
    • Unordered List
    1. Ordered List
    1. Ordered List
    - [ ] Todo List
    • Todo List
    > Blockquote
    Blockquote
    **Bold font** Bold font
    *Italics font* Italics font
    ~~Strikethrough~~ Strikethrough
    19^th^ 19th
    H~2~O H2O
    ++Inserted text++ Inserted text
    ==Marked text== Marked text
    [link text](https:// "title") Link
    ![image alt](https:// "title") Image
    `Code` Code 在筆記中貼入程式碼
    ```javascript
    var i = 0;
    ```
    var i = 0;
    :smile: :smile: Emoji list
    {%youtube youtube_id %} Externals
    $L^aT_eX$ LaTeX
    :::info
    This is a alert area.
    :::

    This is a alert area.

    Versions and GitHub Sync
    Get Full History Access

    • Edit version name
    • Delete

    revision author avatar     named on  

    More Less

    Note content is identical to the latest version.
    Compare
      Choose a version
      No search result
      Version not found
    Sign in to link this note to GitHub
    Learn more
    This note is not linked with GitHub
     

    Feedback

    Submission failed, please try again

    Thanks for your support.

    On a scale of 0-10, how likely is it that you would recommend HackMD to your friends, family or business associates?

    Please give us some advice and help us improve HackMD.

     

    Thanks for your feedback

    Remove version name

    Do you want to remove this version name and description?

    Transfer ownership

    Transfer to
      Warning: is a public team. If you transfer note to this team, everyone on the web can find and read this note.

        Link with GitHub

        Please authorize HackMD on GitHub
        • Please sign in to GitHub and install the HackMD app on your GitHub repo.
        • HackMD links with GitHub through a GitHub App. You can choose which repo to install our App.
        Learn more  Sign in to GitHub

        Push the note to GitHub Push to GitHub Pull a file from GitHub

          Authorize again
         

        Choose which file to push to

        Select repo
        Refresh Authorize more repos
        Select branch
        Select file
        Select branch
        Choose version(s) to push
        • Save a new version and push
        • Choose from existing versions
        Include title and tags
        Available push count

        Pull from GitHub

         
        File from GitHub
        File from HackMD

        GitHub Link Settings

        File linked

        Linked by
        File path
        Last synced branch
        Available push count

        Danger Zone

        Unlink
        You will no longer receive notification when GitHub file changes after unlink.

        Syncing

        Push failed

        Push successfully