Warning
This page is located in archive.

1. Seznámení s učebnou, základy reprezentace dat, číselné soustavy

Osnova cvičení

  1. požadavky na zápočet, hodnocení
  2. seznámení s učebnou
  3. základní reprezentace čísel, program v C
  4. výstup binární hodnoty na port hardware

Co bych si měl na první cvičení zopakovat / připravit

  1. K přihlášení ke GNU/Linuxu v KN:E-2 se používá Hlavní přístupové heslo (více na stránce o přístupových heslech na Wiki IT skupiny)
  2. binární a hexadecimální reprezentace čísel
  3. syntaxe jazyka C
  4. pojmy little/big endian
  5. doplňkový kód

Náplň cvičení

Cílem cvičení je seznámení s učebnou, zopakování základních pojmů na téma reprezentace dat v počítači.

Na cvičení budeme vycházet z následujícího programu v C, který budeme dále modifikovat.

Program pro zobrazování reprezentace čísel:

/* Simple program to examine how are different data types encoded in memory */

#include <stdio.h>

/*
 * The macro determines size of given variable and then
 * prints individual bytes of the value representation
 */
#define PRINT_MEM(a) print_mem((unsigned char*)&(a), sizeof(a))

void print_mem(unsigned char *ptr, int size) {
  int i;
  printf("address = 0x%08lx\n", (long unsigned int)ptr);

  for (i = 0; i < size; i++) {
    printf("0x%02x ", *(ptr+i));
  }

  printf("\n");
}

int main() {
  /* try for more types: long, float, double, pointer */
  unsigned int unsig = 5;
  int sig = -5;

  /* Read GNU C Library manual for conversion syntax for other types */
  /* https://www.gnu.org/software/libc/manual/html_node/Formatted-Output.html */
  printf("value = %d\n", unsig);
  PRINT_MEM(unsig);

  printf("\nvalue = %d\n", sig);
  PRINT_MEM(sig);

  return 0;
}

Překlad programu: gcc -Wall -ansi -pedantic ./program.c

Úkoly

  1. přeložte a spusťte uvedený program
  2. interpretujte výstupy programu
  3. modifikujte program tak, aby tiskl vnitřní reprezentaci i jiných datových typů (např. char, float, long, int*)
  4. modifikujte program tak, aby vytiskl tabulku celých čísel čísel včetně jejich reprezentace v rozsahu -16 až 15
  5. modifikujte program tak, aby realizoval operace sčítání a odčítání dvou proměnných (celá čísla) a vytiskl na obrazovku vstupní operandy a výsledky těchto operací včetně jejich vnitřní reprezentace
  6. vyzkoušejte operace s kladnými i zápornými čísly, zaměřtě se i na takové hodnoty, kdy po provedení operace dojde k přetečení
  7. seznamte se s přípravkem MicroZed APO (MZ_APO) a vyzkoušejte si, že data/čísla jsou do paměti i na periferie ukládaná binárně

Užitečné odkazy

Domácí úkoly

  • domácí úkoly 1 až 4 budou zadané a odevzdávané elektronickou formou
  • vstup k zadání a odevzdání úkolů je přes adresu https://dcenet.felk.cvut.cz/apo/
  • Na stránce “Assignments” naleznete seznam zadaných úkolů
  • Pro vyzkoušení práce s odevzdávacím systémem je k dispozici nehodnocená varianta prvního úkolu 1st training homework
  • Případnými problémy s odevzdávacím systémem se obracejte na svého cvičícího nebo přímo na autora/správce sytému Richarda Šustu

Počítačová síť v učebně KN:E-2

Učebna KN:E-2 je vybavena počítači se síťovou instalací operačního systému Debian GNU/Linux Jessie.

Po zapnutí počítače je zaveden z lokálního serveru přes PXE zavaděč PXElinux. Ten umožňuje volit

  • boot z lokálního disku stanice (nebudeme používat)
  • volba DCE 4.9 stretch app spuštění síťové verze operačního systému Debian Stretch

Volba menu zajistí natažení image jádra GNU/Linux a počátečního RAM-disku ze sítě s využitím protokolu TFTP. Po spuštění jádra je připojen přes NFS kořenový svazek. Ten je ale připojen jen v režimu pro čtení. Pro dočasné uložení lokálních změn je pak nad tuto adresářovou strukturu namapovaný souborový systém pro dočasné uložení lokálních změn. Jedná se buď o modul Overlayfs (dříve se používal AUFS). Pro ověření přihlašovacích údajů se používá systém Kerberos a ověřuje se proti hlavnímu ČVUT heslo. Po úspěšném přihlášení je pak do adresářové struktury stanice připojen přes NFS svazek s uživatelským kontem, ke kterému má uživatel práva pro čtení a zápis.

Další informace o použitém řešení lze nalézt na stránce Wiki Jak vytvořit bezdiskový stroj s operačním systémem GNU/Linux. Dále jsou k dispozici slide DiskLess Debian/GNU Linux z prezentace našeho řešení na konferenci/akci Install Fest.

Studentská konta a hesla

Pro ověření oprávnění k přístupu do systému a se používá centrální ČVUT/FEL heslo.

Vzdálený přístup k datům na uživatelských kontech

Kromě místnosti KN:E-2 je prostředí dostupné v místnosti KN:E-s109 a na externě přístupném serveru postel.felk.cvut.cz přes SSH spojení. Pro přenos souborů lze pak použít příkaz SCP případně jiné klienty podporující SCP/SFTP protokol.

Konto si lze připojit do vlastního GNU/Linux systému přes utilitu sshfs, např.:

sshfs jmeno@postel.felk.cvut.cz: /mnt/tmp
Server nabízí i vzdálené připojení k na něm spouštěným grafickým aplikacím
ssh -X jmeno@postel.felk.cvut.cz

Poznámka: Jméno nebylo zvoleno pouze z důvodu pohodlného přístupu z pohodlí domova, ale je především připomínkou jedné z klíčových osobností počítačové sítě Internet - Jona Postela.

Řešení problémů s konty, obnova dat

V případě problémů s instalací GNU/Linxu kontaktuje Aleše Kapicu ze skupiny IT (http://support.dce.felk.cvut.cz/mediawiki/index.php/IT_kontakty) případně další kolegy ze skupiny IT, kteří mají správu hardware a sítí ne jen v KN:E-2 na starosti.

Seznámení se s výukovou deskou MicroZed APO

  • mz_apo .. popis hardware desky
    • Popis je relativně podrobný a bude postupně doplňovaný o další zajímavé informace
    • Pro účely prvního a druhého cvičení je podstatný pouze seznam registrů ze sekce Blok otočných voličů, maticové klávesnice a RGB LED

Přihlášení

Přes sériový port, parametry:

  • Zařízení: /dev/ttyUSB0
  • Přenosová rychlost: 115200
  • Žádná parita a jeden stopbit
  • Login: root
  • Heslo bude sděleno na cvičení

Po zjištění adresy připojené desky přes SSH

Adresu desky je možné zjistit i z výpisu na obrazovce. Adresa je zobrazená binárně v každé řádce vždy od nejvýznamnějšího bitu (MSB) k bitu nejméně významnému (LSB).

ssh -i /opt/zynq/ssh-connect/mzapo-root-key root@192.168.202.xxx

Pro přenos souborů mezi cílovým systémem a hostitelským PC lze použít program SCP nebo si lze připojit kompletní souborový systém cílové desky do podadresáře /tmp příkazem

/opt/zynq/sshfs-mount-target 192.168.202.xxx

Případně lze privátní klíč přidat do ssh agenta:
ssh-add /opt/zynq/ssh-connect/mzapo-root-key

Připravený příkaz pro rychlé zavedení klíče

ssh-add-mzapo-key

A potom se k systému připojit přes ssh s uživatelem root již bez zadávání klíče nebo hesla:

ssh root@192.168.202.xxx

Binární reprezentace na cílovém systému

Do adresářové struktury cílového systému je do adresářů /opt/apo a /opt/zynq připojený shodný svazek/obsah jako je na hostitelském PC

Nachází se zde i adresář se zdrojovými kódy programu pro test binární reprezentace

/opt/apo/binrep/print_binrep

Adresář vidí jak na host systému (PC) tak na target systému (Zynq). Ale na obou je jen read-only. Pro kompilaci je potřeba adresář zkopírovat a spustit v něm příkaz

cp -r /opt/apo/binrep/print_binrep /root
cd /root/print_binrep
make

Pro vynucení překompilování lze použít cíl clean pro smazání a cíl all pro opětovnou kompilaci. Cíl all je shodný s prvním uvedeným cílem, který je vybraný při volání bez parametrů.

make clean all

Na cílovém systému je přidaná i kopie zdrojového kódu v adresáři /root/apo/binrep/print_binrep, ten je stejně jako většina systému překrytý pod overlayem, takže lze přímo používat. Pozor, po restartu systému budou veškerá data ztracena. Vše se odehrává jen v lokální RAM (tmpfs, zjednodušeně RAM disk). Jednoduchá kompilace a spuštění

cd /root/apo/binrep/print_binrep
make
./print_binrep

Přímo na cílovém systému lze zdrojové kódy editovat programy

  • mcedit
  • vi/vim
  • nano (vhodne pouze pri pripojeni pres ssh)

Ty jsou k dispozici i na hostitelském PC, kde jsou navíc nainstalované i editory/prostředí

  • geanny
  • eclipse

Přístup k periferiím na cílovém systému

Jednoduchý program, který kopíruje hodnotu z RGB voličů na RGB LED a řádku LED diod.

Program naleznete v adresáři /opt/apo/binrep/mzapo_binrep a na cílovém systému i v adresáři /root/apo/binrep/print_binrep.

/*******************************************************************
  Simple program to demostrate binary reprezentation on MicroZed
  based MZ_APO board designed by Petr Porazil at PiKRON

  mzapo_binrep.c       - main and only file

  (C) Copyright 2004 - 2017 by Pavel Pisa
      e-mail:   pisa@cmp.felk.cvut.cz
      homepage: http://cmp.felk.cvut.cz/~pisa
      work:     http://www.pikron.com/
      license:  any combination GPL, LGPL, MPL or BSD licenses

 *******************************************************************/

#define _POSIX_C_SOURCE 200112L

#include <sys/mman.h>
#include <stdlib.h>
#include <stdio.h>
#include <stdint.h>
#include <unistd.h>
#include <fcntl.h>
#include <malloc.h>
#include <string.h>
#include <byteswap.h>
#include <getopt.h>
#include <inttypes.h>
#include <time.h>

char *memdev="/dev/mem";

/*
 * Next macros provides location of knobs and LEDs peripherals
 * implemented in MZ_APO FPGA design.
 *
 * The complete list of peripheral implemented in the design
 * can be found on the page
 *   https://cw.fel.cvut.cz/wiki/courses/b35apo/documentation/mz_apo/start
 */

/*
 * Base address of the region used for mapping of the knobs and LEDs
 * peripherals in the ARM Cortex-A9 physical memory address space.
 */
#define SPILED_REG_BASE_PHYS 0x43c40000

/* Valid address range for the region */
#define SPILED_REG_SIZE      0x00004000

/*
 * Byte offset of the register which controls individual LEDs
 * in the row of 32 yellow LEDs. When the corresponding bit
 * is set (value 1) then the LED is lit.
 */
#define SPILED_REG_LED_LINE_o           0x004

/*
 * The register to control 8 bit RGB components of brightness
 * of the first RGB LED
 */
#define SPILED_REG_LED_RGB1_o           0x010

/*
 * The register to control 8 bit RGB components of brightness
 * of the second RGB LED
 */
#define SPILED_REG_LED_RGB2_o           0x014

/*
 * The register which combines direct write to RGB signals
 * of the RGB LEDs, write to the keyboard scan register
 * and control of the two additional individual LEDs.
 * The direct write to RGB signals is orred with PWM
 * signal generated according to the values in previous
 * registers.
 */
#define SPILED_REG_LED_KBDWR_DIRECT_o   0x018

/*
 * Register providing access to unfiltered encoder channels
 * and keyboard return signals.
 */
#define SPILED_REG_KBDRD_KNOBS_DIRECT_o 0x020

/*
 * The register representing knobs positions as three
 * 8-bit values where each value is incremented
 * and decremented by the knob relative turning.
 */
#define SPILED_REG_KNOBS_8BIT_o         0x024


/*
 * The support function which returns pointer to the virtual
 * address at which starts remapped physical region in the
 * process virtual memory space.
 */
void *map_phys_address(off_t region_base, size_t region_size, int opt_cached)
{
  unsigned long mem_window_size;
  unsigned long pagesize;
  unsigned char *mm;
  unsigned char *mem;
  int fd;

  /*
   * Open a device ("/dev/mem") representing physical address space
   * in POSIX systems
   */
  fd = open(memdev, O_RDWR | (!opt_cached? O_SYNC: 0));
  if (fd < 0) {
    fprintf(stderr, "cannot open %s\n", memdev);
    return NULL;
  }

  /*
   * The virtual to physical address mapping translation granularity
   * corresponds to memory page size. This call obtains the page
   * size used by running operating system at given CPU architecture.
   * 4kB are used by Linux running on ARM, ARM64, x86 and x86_64 systems.
   */
  pagesize=sysconf(_SC_PAGESIZE);

  /*
   * Extend physical region start address and size to page size boundaries
   * to cover complete requested region.
   */
  mem_window_size = ((region_base & (pagesize-1)) + region_size + pagesize-1) & ~(pagesize-1);

  /*
   * Map file (in our case physical memory) range at specified offset
   * to virtual memory ragion/area (see VMA Linux kernel structures)
   * of the process.
   */
  mm = mmap(NULL, mem_window_size, PROT_WRITE|PROT_READ,
              MAP_SHARED, fd, region_base & ~(pagesize-1));

  /* Report failure if the mmap is not allowed for given file or its region */
  if (mm == MAP_FAILED) {
    fprintf(stderr,"mmap error\n");
    return NULL;
  }

  /*
   * Add offset in the page to the returned pointer for non-page-aligned
   * requests.
   */
  mem = mm + (region_base & (pagesize-1));

  return mem;
}

/*
 * The main entry into example program
 */
int main(int argc, char *argv[])
{

  unsigned char *mem_base;

  /*
   * Setup memory mapping which provides access to the peripheral
   * registers region of RGB LEDs, knobs and line of yellow LEDs.
   */
  mem_base = map_phys_address(SPILED_REG_BASE_PHYS, SPILED_REG_SIZE, 0);

  /* If mapping fails exit with error code */
  if (mem_base == NULL)
    exit(1);

  while (1) {
     uint32_t rgb_knobs_value;
     int int_val;
     unsigned int uint_val;

     /* Initialize structure to 0 seconds and 200 milliseconds */
     struct timespec loop_delay = {.tv_sec = 0, .tv_nsec = 200 * 1000 * 1000};

     /*
      * Access register holding 8 bit relative knobs position
      * The type "(volatile uint32_t*)" casts address obtained
      * as a sum of base address and register offset to the
      * pointer type which target in memory type is 32-bit unsigned
      * integer. The "volatile" keyword ensures that compiler
      * cannot reuse previously read value of the location.
      */
     rgb_knobs_value = *(volatile uint32_t*)(mem_base + SPILED_REG_KNOBS_8BIT_o);

     /* Store the read value to the register controlling individual LEDs */
     *(volatile uint32_t*)(mem_base + SPILED_REG_LED_LINE_o) = rgb_knobs_value;

     /*
      * Store RGB knobs values to the corersponding components controlling
      * a color/brightness of the RGB LEDs
      */
     *(volatile uint32_t*)(mem_base + SPILED_REG_LED_RGB1_o) = rgb_knobs_value;

     *(volatile uint32_t*)(mem_base + SPILED_REG_LED_RGB2_o) = rgb_knobs_value;

     /* Assign value read from knobs to the basic signed and unsigned types */
     int_val = rgb_knobs_value;
     uint_val = rgb_knobs_value;

     /* Print values */
     printf("int %10d uint 0x%08x\n", int_val, uint_val);

     /*
      * Wait for time specified by "loop_delay" variable.
      * Use monotonic clocks as time reference to ensure
      * that wait interval is not prolonged or shortened
      * due to real time adjustment.
      */
     clock_nanosleep(CLOCK_MONOTONIC, 0, &loop_delay, NULL);
  }

  return 0;
}

Klíčová sekvence kódu přístupu k periferiím, jak jí vidí procesor

Program je zkompilovaný překladačem GCC pro architekturu ARM Cortex-A (arm-linux-gnueabihf-gcc). Pro zkrácení kódu je využitá varianta kódování instrukcí Thumb, která kombinuje instrukce kódované do 16 a 32-bitů. Pro dekódování byl použitý nástroj objdump

arm-linux-gnueabihf-objdump --source mzapo_binrep | less

Při kompilaci nebyla požadovaná žádná optimalizace, proto kód opakovaně přistupuje do lokálních proměnných a řádky v assembleru lze velmi dobře porovnávat s řádky zdrojového kódu.

Pro případ bez optimalizace

     rgb_knobs_value = *(volatile uint32_t*)(mem_base + SPILED_REG_KNOBS_8BIT_o);
                           load value of the local variable "mem_base" from the
                           memory location relative to the register r7
   106e6:       69fb            ldr     r3, [r7, #28]
                           read value from the address corresponding to the
                           sum of "mem_base" and "SPILED_REG_KNOBS_8BIT_o"
                           peripheral register offset
   106e8:       6a5b            ldr     r3, [r3, #36]   ; 0x24
                           store read value to the local variable "rgb_knobs_value"
   106ea:       61bb            str     r3, [r7, #24]

     *(volatile uint32_t*)(mem_base + SPILED_REG_LED_LINE_o) = rgb_knobs_value;
                           read "mem_base"
   106ec:       69fb            ldr     r3, [r7, #28]
                           use short form (Thumb) instruction to add "SPILED_REG_LED_LINE_o"
   106ee:       3304            adds    r3, #4
                           read "rgb_knobs_value" local variable
   106f0:       69ba            ldr     r2, [r7, #24]
                           store "rgb_knobs_value" to the LED line peripheral register
   106f2:       601a            str     r2, [r3, #0]

     *(volatile uint32_t*)(mem_base + SPILED_REG_LED_RGB1_o) = rgb_knobs_value;
                           load "mem_base"
   106f4:       69fb            ldr     r3, [r7, #28]
                           add offset of the first RGB LED
   106f6:       3310            adds    r3, #16
                           load "rgb_knobs_value"
   106f8:       69ba            ldr     r2, [r7, #24]
                           store value to control color
   106fa:       601a            str     r2, [r3, #0]

     *(volatile uint32_t*)(mem_base + SPILED_REG_LED_RGB2_o) = rgb_knobs_value;
                           load "mem_base"
   106fc:       69fb            ldr     r3, [r7, #28]
                           add offset of the second RGB LED
   106fe:       3314            adds    r3, #20
                           load "rgb_knobs_value"
   10700:       69ba            ldr     r2, [r7, #24]
                           store value to control color
   10702:       601a            str     r2, [r3, #0]

     int_val = rgb_knobs_value;
                           load "rgb_knobs_value"
   10704:       69bb            ldr     r3, [r7, #24]
                           store value to "int_val"
   10706:       617b            str     r3, [r7, #20]
                           no conversion between unsigned
                           32-bit value and signed signed int.
                           They are of the same size and there
                           is no conversion between the unsigned
                           and the second complement signed representation.
                           The half range silently overflows/wraps.

     uint_val = rgb_knobs_value;
                           load "rgb_knobs_value"
   10708:       69bb            ldr     r3, [r7, #24]
                           store value to "uint_val"
   1070a:       613b            str     r3, [r7, #16]
                           no conversion, the same size

     printf("int %10d uint 0x%08x\n", int_val, uint_val);
                           the first argument register (r0 .. a0)
                           is set to the start address of the format string
   1070c:       f240 70ac       movw    r0, #1964       ; 0x7ac
   10710:       f2c0 0001       movt    r0, #1
                           load "int_val" into the second argument register (r1 .. a1)
   10714:       6979            ldr     r1, [r7, #20]
                           load "uint_val" into the third argument register (r2 .. a2)
   10716:       693a            ldr     r2, [r7, #16]
                           call the function
   10718:       f7ff eea8       blx     1046c <printf@plt>

Pro případ se základní optimalizací -O1

  Setup memory mapping which provides access to the peripheral
  registers region of RGB LEDs, knobs and line of yellow LEDs.
  mem_base = map_phys_address(SPILED_REG_BASE_PHYS, SPILED_REG_SIZE, 0);
   106b8:	2200      	movs	r2, #0
   106ba:	f44f 4180 	mov.w	r1, #16384	; 0x4000
   106be:	4610      	mov	r0, r2
                                  argument 0 (r0) set to physical base
                                  of the SPILED registers block. Use instruction
                                  to set upper 16 bits of the register
   106c0:	f2c4 30c4 	movt	r0, #17348	; 0x43c4
   106c4:	f7ff ffa4 	bl	10610 <map_phys_address>

  /* If mapping fails exit with error code */
  if (mem_base == NULL)
   106c8:	b910      	cbnz	r0, 106d0 <main+0x1c>
    exit(1);
   106ca:	2001      	movs	r0, #1
   106cc:	f7ff ef18 	blx	10500 <exit@plt>

                                  else setup r4 to virtual base of the mapping
   106d0:	4604      	mov	r4, r0

                                  prepare address of text "int %10d uint 0x%08x\n"
                                  outside of the loop, the 16 lower bits the first
   106dc:	f240 7670 	movw	r6, #1904	; 0x770
                                  set upper 16 bits   
   106e0:	f2c0 0601 	movt	r6, #1

                                  prepare value for struct timespec loop_delay   
   106e4:	9500      	str	r5, [sp, #0]
   106e6:	9701      	str	r7, [sp, #4]

     rgb_knobs_value = *(volatile uint32_t*)(mem_base + SPILED_REG_KNOBS_8BIT_o);
                           read value from the address corresponding to the
                           sum of "mem_base" and "SPILED_REG_KNOBS_8BIT_o"
                           peripheral register offset
   106e8:	6a61      	ldr	r1, [r4, #36]	; 0x24
     *(volatile uint32_t*)(mem_base + SPILED_REG_LED_LINE_o) = rgb_knobs_value;
                           store "rgb_knobs_value" to the LED line peripheral register
   106ea:	6061      	str	r1, [r4, #4]
     *(volatile uint32_t*)(mem_base + SPILED_REG_LED_RGB1_o) = rgb_knobs_value;
                           store value to color register LED1
   106ec:	6121      	str	r1, [r4, #16]
     *(volatile uint32_t*)(mem_base + SPILED_REG_LED_RGB2_o) = rgb_knobs_value;
                           store value to color register LED2
   106ee:	6161      	str	r1, [r4, #20]
     printf("int %10d uint 0x%08x\n", int_val, uint_val);
                           copy value of argument 1 (r1) even to argument 2 (r2)  
   106f0:	460a      	mov	r2, r1
                           set argument r0 from saved register r6
   106f2:	4630      	mov	r0, r6
   106f4:	f7ff eeda 	blx	104ac <printf@plt>

Šablony aplikací pro cílový systém

Šablona aplikace s definovaným cílem/pravidlem pro spouštění aplikace na vzdáleném systému se nachází v adresáři /opt/apo/mzapo_start. Rozšířená šablona i s definicemi struktur pro zobrazení znaků/textu na displeji /opt/apo/mzapo_template. Aplikace pro zobrazení čísel /opt/apo/binrep/mzapo_binrep. Příklad

ssh-add-mzapo-key
cd ~/Documents
cp -r /opt/apo/binrep/mzapo_binrep .
cd mzapo_binrep
make
TARGET_IP=192.168.202.xxx make run

Případně debug místo run pro ladění.

I další šablony kromě kompilace programu obsahují i cíl/pravidlo pro spouštění programu. Kromě požadavku na kompilaci a spuštění je potřeba uvést adresu desky, na které se má zkompilovaná aplikace pustit

TARGET_IP=192.168.202.xxx make run

Pro ladění je připravené pravidlo, které po kompilaci zkopíruje program, spustí ho v ladícím režimu na cílové desce a na hostitelském počítači pustí ladící nástroj GDB s grafickou nadstavbou DDD.

TARGET_IP=192.168.202.xxx make debug

Úkol na zamyšlení do příštího cvičení

Vlastní poloha voličů sice přibývá po jedné, ale aretace se nachází vždy na každé čtvrté poloze. Úkolem je upravit program tak, aby při otočení o čtyři původní kroky (z aretace na aretaci) přibyla hodnota G a B vždy jen o jedničku. Zároveň je požadované, aby byl zachovaný rozsah hodnot 0 až 255 při delším otáčení voliče.

Na řádku led pak budou vypsané oba osmibitové vstupy a šestnáctibitově jejich součet. Experimentujete se sčítáním osmibitových typů signed char / usigned char (případně pro daný cílový systém ekvivalentu int8_t a uint8_t).

courses/b35apo/tutorials/01/start.txt · Last modified: 2018/02/21 13:07 by pisa