Dydaktyka
      • 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
        • Owners
        • Signed-in users
        • Everyone
        Owners Signed-in users Everyone
      • Write
        • Owners
        • Signed-in users
        • Everyone
        Owners 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 New
    • Engagement control
    • Make a copy
    • Transfer ownership
    • Delete this note
    • Insert from template
    • Import from
      • Dropbox
      • Google Drive
      • Gist
      • Clipboard
    • Export to
      • Dropbox
      • Google Drive
      • Gist
    • Download
      • Markdown
      • HTML
      • Raw HTML
Menu Note settings Note Insights Versions and GitHub Sync Sharing URL Help
Menu
Options
Engagement control Make a copy 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
Owners
  • Owners
  • Signed-in users
  • Everyone
Owners Signed-in users Everyone
Write
Owners
  • Owners
  • Signed-in users
  • Everyone
Owners 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
    • Any changes
      Be notified of any changes
    • Mention me
      Be notified of mention me
    • Unsubscribe
    # Ćwiczenia 13, grupa cz. 10-12, 26 stycznia 2023 ###### tags: `SO22` `ćwiczenia` `pwit` ## Deklaracje Gotowość rozwiązania zadania należy wyrazić poprzez postawienie X w odpowiedniej kolumnie! Jeśli pożądasz zreferować dane zadanie (co najwyżej jedno!) w trakcie dyskusji oznacz je znakiem ==X== na żółtym tle. **UWAGA: Tabelkę wolno edytować tylko wtedy, gdy jest na zielonym tle!** :::warning **Uwaga**: Dzisiaj deklarujemy zadania **5 -- 9** z **listy 12** oraz **1 -- 4** z **listy 13**. Formularz deklaracji **listy 12**: [https://hackmd.io/@iiuwr-kba/r1T8KdIio/edit](https://hackmd.io/@iiuwr-kba/r1T8KdIio/edit) ::: :::danger | | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | | ----------------------:| ----- | --- | --- | --- | --- | --- | --- | --- | Miriam Bouhajeb | X | | | | | | | | Kacper Chmielewski | X | X | | X | | | | | Jan Jankowicz | X | X | | | X | X | X | X | Jakub Kaczmarek | | | | | | | | | Jarosław Kadecki | | | | | X | | | | Zuzanna Kania | X | | | | X | X | | X | Julia Konefał | | | | | | | | | Daniel Sarniak | X | X | | | X | X | | X | Paweł Tkocz | | | | | X | X | X | X | Miłosz Urbanik | X | X | X | | X | X | | X | Tomasz Wołczański | X | X | | | | X | | | Radosław Śliwiński | | | | | | | | | ::: :::info **Uwaga:** Po rozwiązaniu zadania należy zmienić kolor nagłówka na zielony. ::: ## Zadanie 1 :::success Autor: Zuzanna Kania ![](https://i.imgur.com/Ki396kI.png) **Mutex** to specjalna struktura służąca do synchronizacji wątków. Działaniem przypomina semafor binarny, jednak oprócz tego pamięta jeszcze wątek, który go zajął, dzięki czemu możliwe jest działanie dziedziczenia priorytetów oraz możliwe jest kontrolowanie tego, czy wątkiem zwalniającym blokadę jest ten, który ją zajął. **Odwrócenie priorytetów** polega na tym, że wątek o wysokim priorytecie zostaje zmuszony do uśpienia pomimo swojego pierwszeństwa tylko dlatego, że wątek o niższym priorytecie zajął mutex i został wywłaszczony zanim zdążył go zwolnić. **Dziedziczenie priorytetów** to metoda rozwiązywania powyższego problemu. Polega na tym, że wątek o wysokim priorytecie udziela swojego priorytetu wątkowi o priorytecie niższym (korzystając z tego, że mutex przechowuje informację o tym, który wątek go zajmuje). Taki pożyczony priorytet jest "oddawany" po zwolnieniu mutexa, co umożliwia wątkowi o wysokim priorytecie kontynuowanie wykonywania własnego kodu. ![](https://i.imgur.com/GpctOos.png) ![](https://i.imgur.com/HBTCaRU.png) Jednym z rozwiązań zapobiegających wystąpieniu zjawiska odwrócenia priorytetów jest ustawienie możliwie najwyższego priorytetu wątkowi, który zajął zamek, i przywrócenie ich, gdy ten odda zamek. Tak więc implementacja `mutex_lock` powinna zawierać wywołanie funkcji `pthread_setschedprio` z parametrem zawierającym maksymalny priorytet dla danej polityki planisty, a `mutex_unlock` powinno zawierać wywołanie tej samej funkcji z poprzednią wartością priorytetu. **Czy semafory są odporne na problem odwrócenia priorytetów?** Nie, ponieważ semafor nie wie, które wątki go zajmują i tym samym czekający wątek o wysokim priorytecie nie jest w stanie wpłynąć na blokujące go wątki o niższym priorytecie - jest zmuszony czekać. ::: ## Zadanie 2 :::success Autor: Daniel Sarniak ![](https://i.imgur.com/w1pjige.png) ~~Założenie, że `pthread_cond_signal` budzi tylko jeden wątek (błędne założenie)~~ > [name=Piotr Witkowski] Wbrew temu, co stwierdziłem na ćwiczeniach, powyższe założenie nie jest wymagane. Zmienna warunkowa `waiters` jest przecież związana z muteksem `mutex`. Każdy, z być może wielu procesów wybudzonych przez `pthread_cond_signal`, zanim powróci z wywołania `pthread_cond_wait`, musi odzyskać `mutex`. Zatem test warunku w wierszu 23 będzie odbywać się w sposób uszeregowany, a nie współbieżny. > Rozważmy przykład z trzema wątkami A, B, C i początkową wartością `value` równą 1: > 1. wątek A wykonuje `sem_wait(s)` i zajmuje semafor, `value == 0` > 2. wątek B wykonuje `sem_wait(s)` i usypia się w wierszu 24 > 3. wątek C wykonuje `sem_wait(s)` i usypia się w wierszu 24 > 4. wątek A wykonuje `sem_post(s)`, czyli ustawia value na 1, oraz budzi wątki B i C funkcją `pthread_cond_signal` > 5. wątki B i C rozpoczynają rywalizację o `mutex`, załóżmy, że wygrywa ją B > 6. wątek B powraca z wywołania `pthread_cond_wait` i kontynuuje wykonanie `sem_wait(s)`, ustawia value na 0 i zajmuje semafor > 7. wątek C zajmuje `mutex` zwolniony w poprzednim punkcie przez B i powraca z wywołania `pthread_cond_wait`. W wierszu 23 okazuje się, że `value == 0`, więc C ponownie wywołuje `pthread_cond_wait` i usypia się w wierszu 24. > 8. wątek C będzie miał szansę na kontynuację dopiero po wywołaniu `sem_post(s)` przez wątek B. > Współbieżne wykonanie wierszy 23-24 przez B i C nie jest możliwe, gdyż tylko jeden z nich (w naszym przykładzie B) może w danym momencie zajmować `mutex`. > Rozwiązanie jest zatem poprawne. ```c= typedef struct Sem { pthread_mutex_t mutex; pthread_cond_t waiters; int value; } Sem_t; ``` ```c= typedef struct Sem { pthread_mutex_t mutex; pthread_cond_t waiters; int value; } Sem_t; void sem_init(Sem_t* s, int value) { assert(value >= 0); s->value = value; pthread_mutex_init(&s->mutex, NULL); pthread_cond_init(&s->waiters, NULL); } void sem_post(Sem_t* s) { pthread_mutex_lock(&s->mutex); s->value++; pthread_cond_signal(*s->waiters); pthread_mutex_unlock(&s->mutex); } void sem_wait(Sem_t* s) { pthread_mutex_lock(&s->mutex); while (s->value == 0) { pthread_cond_wait(&s->waiters, &s->mutex); } s->value--; pthread_mutex_unlock(&s->mutex); } ``` `int pthread_cond_wait(pthread_cond_t *cond, pthread_mutex_t *mutex)` used to block on a condition variable. They are called with mutex locked by the calling thread or undefined behaviour will result. These functions atomically release mutex and cause the calling thread to block on the condition variable cond; atomically here means "atomically with respect to access by another thread to the mutex and then the condition variable" `int pthread_cond_signal(pthread_cond_t *cond)` The pthread_cond_signal() call unblocks at least one of the threads that are blocked on the specified condition variable cond (if any threads are blocked on cond). `int pthread_cond_init(pthread_cond_t *cond, const pthread_condt_t *attr);` The function pthread_cond_init() initialises the condition variable referenced by cond with attributes referenced by attr. If attr is NULL, the default condition variable attributes are used; the effect is the same as passing the address of a default condition variable attributes object `int pthread_mutex_init(pthread_mutex_t *mutex, const pthread_mutex_t *attr);` The pthread_mutex_init() function initialises the mutex referenced by mutex with attributes specified by attr. If attr is NULL, the default mutex attributes are used; the effect is the same as passing the address of a default mutex attributes object. Upon successful initialisation, the state of the mutex becomes initialised and unlocked. `pthread_mutex_lock / pthread_mutex_unlock` Releases a mutex object. If one or more threads are waiting to lock the mutex, pthread_mutex_unlock() causes one of those threads to return from pthread_mutex_lock() with the mutex object acquired. If no threads are waiting for the mutex, the mutex unlocks with no current owner. ::: ## Zadanie 3 :::success Autor: Miłosz Urbanik![](https://i.imgur.com/VLdONHF.png) ::: Syscall `futex` (fast user-space locking/mutex) służy implementacji niskopoziomowych blokad, które działają w przestrzeni użytkownika. `FUTEX_WAIT` - sprawia, że wątek/proces jest dodawany do kolejki w przestrzeni jądra `FUTEX_WAKE` - budzi n wątków z kolejki Jądro identyfikuje kolejki na podstawie adresu zmiennej, które przechowuje wartość blokady * **blokada adaptacyjna** - blokada, która w swojej implementacji zawiera różne strategie blokowania/odblokowania w zależności od stopnia współzawodnictwa, wykorzystania procesora, aktualnie wykonywanego wątku. * **współzawodnictwo** - określenie na liczbę procesów/wątków chcących otrzymać dostęp do zasobu Wartość blokady futex określa (niewprost) liczbę wątków, które oczekują na blokadę. W warunkach wysokiego współzawodnictwa blokada wykonuje syscall `FUTEX_WAIT` > [name=Piotr Witkowski] Alternatywna, bardziej czytelna implementacja zamka opartego na futeksie > [https://pages.cs.wisc.edu/~remzi/OSTEP/threads-locks.pdf](https://pages.cs.wisc.edu/~remzi/OSTEP/threads-locks.pdf), str. 19, rysunek 28.10 ## Zadanie 4 :::success Autor: Kacper Chmielewski ![](https://i.imgur.com/7x1NXXh.png) 1. Między liniami 5, a 6 jest szansa na to, że niektóre wątki zakończą się przed obudzeniem innego wątka, co doprowadzi do uśpienia tego jednego na zawsze 2. Proces, który będzie w miejscu wywołania block.wait(), może wykonywać się dlużej niz inny proces, kolejny proces, co może doprowadzić do zaprzeczenia porządku fifo ::: :::info Deklarowanie i prezentacja poniższych zadań przeniesione na następne ćwiczenia. ::: ## Zadanie 5 :::success Autor: Daniel Sarniak ![](https://i.imgur.com/rjBleqS.png) ```c= #include "csapp.h" static __unused void outc(char c) { Write(STDOUT_FILENO, &c, 1); } static void randsleep(void) { usleep(rand() % 5000 + 5000); } #define N 50 static pthread_t td[N]; static sem_t forks[N]; /* TODO: If you need extra shared state, define it here. */ static sem_t mutex; /* ====================================================== */ void *philosopher(void *id) { int right = (intptr_t)id; int left = right == 0 ? N - 1 : right - 1; for (;;) { /* Think */ randsleep(); /* TODO: Take forks (without deadlock & starvation) */ Sem_wait(&mutex); Sem_wait(&forks[right]); Sem_wait(&forks[left]); printf("[%02d] Eating using chopsticks [%02d] and [%02d]\n", right, left, right); /* Eat */ randsleep(); printf("[%02d] Finished using chopsticks [%02d] and [%02d]\n", right, left, right); /* TODO: Put forks (without deadlock & starvation) */ Sem_post(&forks[left]); Sem_post(&forks[right]); Sem_post(&mutex); } return NULL; } int main(void) { /* TODO: If you need extra shared state, initialize it here. */ Sem_init(&mutex, 0, N - 1); /* ====================================================== */ for (int i = 0; i < N; i++) Sem_init(&forks[i], 0, 1); for (int i = 0; i < N; i++) Pthread_create(&td[i], NULL, philosopher, (void *)(intptr_t)i); for (int i = 0; i < N; i++) Pthread_join(td[i], NULL); return EXIT_SUCCESS; } ``` ::: ## Zadanie 6 :::success Autor: Tomasz Wołczański ::: ![](https://i.imgur.com/mZfgEDz.png) ```c= #include "csapp.h" static __unused void outc(char c) { Write(STDOUT_FILENO, &c, 1); } #define N 100 #define M 100 static struct { /* TODO: Put semaphores and shared variables here. */ int portions; sem_t empty; sem_t full; sem_t mutex; } *shared = NULL; static void savage(void) { for (;;) { /* TODO Take a meal or wait for it to be prepared. */ Sem_wait(&shared->mutex); if (shared->portions == 0) { Sem_post(&shared->empty); Sem_wait(&shared->full); } shared->portions--; Sem_post(&shared->mutex); /* Sleep and digest. */ usleep(rand() % 1000 + 1000); } exit(EXIT_SUCCESS); } static void cook(void) { for (;;) { /* TODO Cook is asleep as long as there are meals. * If woken up they cook exactly M meals. */ Sem_wait(&shared->empty); shared->portions = M; Sem_post(&shared->full); } } /* Do not bother cleaning up after this process. Let's assume that controlling * terminal sends SIGINT to the process group on CTRL+C. */ int main(void) { shared = Mmap(NULL, getpagesize(), PROT_READ|PROT_WRITE, MAP_ANON|MAP_SHARED, -1, 0); /* TODO: Initialize semaphores and other shared state. */ shared->portions = M; Sem_init(&shared->empty, 1, 0); Sem_init(&shared->full, 1, 0); Sem_init(&shared->mutex, 1, 1); for (int i = 0; i < N; i++) if (Fork() == 0) savage(); cook(); return EXIT_SUCCESS; } ``` ## Zadanie 7 :::success Autor: Paweł Tkocz ::: ![](https://i.imgur.com/jBJqjaa.png) ```c= #include "csapp.h" static __unused void outc(char c) { Write(STDOUT_FILENO, &c, 1); } typedef struct { /* TODO: Use this structure to store barrier internal state. */ int waiting; int max_count; int started; int finished; sem_t mutex; sem_t barrier; sem_t barrier2; } barrier_t; static barrier_t *barrier_init(int n) { if (n < 1) app_error("barrier_init requires n > 0"); barrier_t *b = Mmap(NULL, sizeof(barrier_t), PROT_READ|PROT_WRITE, MAP_ANON|MAP_SHARED, -1, 0); /* TODO: Initialize barrier internal state. */ Sem_init(&b->barrier, 1, 0); Sem_init(&b->barrier2, 1, 0); Sem_init(&b->mutex, 1, 1); b->waiting = 0; b->finished = 0; b->max_count = n; b->started = 0; return b; } static void barrier_wait(barrier_t *b) { /* TODO: Provide wait procedure implementation here. */ Sem_wait(&b->mutex); b->waiting += 1; if(b->waiting==b->max_count){ Sem_post(&b->barrier); b->waiting = 0; } Sem_post(&b->mutex); Sem_wait(&b->barrier); Sem_wait(&b->mutex); b->started += 1; if(b->started == b->max_count){ b->started = 0; Sem_post(&b->barrier2); } else Sem_post(&b->barrier); Sem_post(&b->mutex); Sem_wait(&b->barrier2); Sem_wait(&b->mutex); b->finished += 1; outc('*'); if(b->finished != b->max_count) Sem_post(&b->barrier2); else{ b->finished = 0; outc(10); } Sem_post(&b->mutex); } static void barrier_destroy(barrier_t *b) { /* TODO: Provide destroy procedure implementation here. */ sem_destroy(&b->barrier2); sem_destroy(&b->barrier); sem_destroy(&b->mutex); } #define K 100 #define N 50 #define P 100 static noreturn void horse(barrier_t *b) { int n = rand() % K + K; outc('+'); for (int i = 0; i < n; i++) { barrier_wait(b); usleep(rand() % 2000 + 1000); } outc('-'); exit(EXIT_SUCCESS); } /* Do not bother cleaning up after this process. Let's assume that controlling * terminal sends SIGINT to the process group on CTRL+C. */ int main(void) { barrier_t *b = barrier_init(N); int horses = 0; for (;;) { do { if (Fork() == 0) { srand(getpid()); horse(b); } horses++; } while (horses < P); Wait(NULL); horses--; } barrier_destroy(b); return EXIT_SUCCESS; } ``` ## Zadanie 8 :::success Autor: Jan Jankowicz ::: ![](https://i.imgur.com/3KiKIrL.png) ```c= #include "csapp.h" static __unused void outc(char c) { Write(STDOUT_FILENO, &c, 1); } static __thread unsigned seed; static sem_t tobacco; static sem_t matches; static sem_t paper; static sem_t doneSmoking; /* TODO: If you need any extra global variables, then define them here. */ static sem_t smokerWithMatches_sem; static sem_t smokerWithTobacco_sem; static sem_t smokerWithPaper_sem; static sem_t checker_sem; static bool matchesPresent; static bool tobaccoPresent; static bool paperPresent; static void *agent(void *arg) { seed = pthread_self(); while (true) { Sem_wait(&doneSmoking); int choice = rand_r(&seed) % 3; if (choice == 0) { Sem_post(&tobacco); Sem_post(&paper); } else if (choice == 1) { Sem_post(&tobacco); Sem_post(&matches); } else { Sem_post(&paper); Sem_post(&matches); } } return NULL; } /* TODO: If you need extra threads, then define their main procedures here. */ static void *matchesChecker(void *arg) { seed = pthread_self(); while (true) { /* wait for matches */ Sem_wait(&matches); Sem_wait(&checker_sem); matchesPresent = true; if (tobaccoPresent) { Sem_post(&smokerWithPaper_sem); tobaccoPresent = false; matchesPresent = false; } if (paperPresent) { Sem_post(&smokerWithTobacco_sem); paperPresent = false; matchesPresent = false; } Sem_post(&checker_sem); } return NULL; } static void *tobaccoChecker(void *arg) { seed = pthread_self(); while (true) { /* wait for tobacco */ Sem_wait(&tobacco); Sem_wait(&checker_sem); tobaccoPresent = true; if (matchesPresent) { Sem_post(&smokerWithPaper_sem); matchesPresent = false; tobaccoPresent = false; } if (paperPresent) { Sem_post(&smokerWithMatches_sem); paperPresent = false; tobaccoPresent = false; } Sem_post(&checker_sem); } return NULL; } static void *paperChecker(void *arg) { seed = pthread_self(); while (true) { /* wait for paper */ Sem_wait(&paper); Sem_wait(&checker_sem); paperPresent = true; if (tobaccoPresent) { Sem_post(&smokerWithMatches_sem); tobaccoPresent = false; paperPresent = false; } if (matchesPresent) { Sem_post(&smokerWithTobacco_sem); matchesPresent = false; paperPresent = false; } Sem_post(&checker_sem); } return NULL; } static void randsleep(void) { usleep(rand_r(&seed) % 1000 + 1000); } static void make_and_smoke(char smoker) { randsleep(); Sem_post(&doneSmoking); outc(smoker); randsleep(); } static void *smokerWithMatches(void *arg) { seed = pthread_self(); while (true) { /* TODO: wait for paper and tobacco */ Sem_wait(&smokerWithMatches_sem); make_and_smoke('M'); Sem_post(&smokerWithMatches_sem); } return NULL; } static void *smokerWithTobacco(void *arg) { seed = pthread_self(); while (true) { /* TODO: wait for paper and matches */ Sem_wait(&smokerWithTobacco_sem); make_and_smoke('T'); Sem_post(&smokerWithTobacco_sem); } return NULL; } static void *smokerWithPaper(void *arg) { seed = pthread_self(); while (true) { /* TODO: wait for tobacco and matches */ Sem_wait(&smokerWithPaper_sem); make_and_smoke('P'); Sem_post(&smokerWithPaper_sem); } return NULL; } int main(void) { Sem_init(&tobacco, 0, 0); Sem_init(&matches, 0, 0); Sem_init(&paper, 0, 0); Sem_init(&doneSmoking, 0, 1); /* TODO: Initialize your global variables here. */ matchesPresent = false; tobaccoPresent = false; paperPresent = false; Sem_init(&checker_sem, 0, 1); Sem_init(&smokerWithMatches_sem, 0, 0); Sem_init(&smokerWithTobacco_sem, 0, 0); Sem_init(&smokerWithPaper_sem, 0, 0); pthread_t paperCheckerThread, matchesCheckerThread, tobaccoCheckerThread; Pthread_create(&paperCheckerThread, NULL, paperChecker, NULL); Pthread_create(&matchesCheckerThread, NULL, matchesChecker, NULL); Pthread_create(&tobaccoCheckerThread, NULL, tobaccoChecker, NULL); pthread_t agentThread; Pthread_create(&agentThread, NULL, agent, NULL); pthread_t smokerPaperThread, smokerMatchesThread, smokerTobaccoThread; Pthread_create(&smokerPaperThread, NULL, smokerWithPaper, NULL); Pthread_create(&smokerMatchesThread, NULL, smokerWithMatches, NULL); Pthread_create(&smokerTobaccoThread, NULL, smokerWithTobacco, NULL); Pthread_join(agentThread, NULL); Pthread_join(smokerPaperThread, NULL); Pthread_join(smokerMatchesThread, NULL); Pthread_join(smokerTobaccoThread, NULL); return 0; } ```

    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