This is an old revision of the document!


Înainte de laborator

Comandă Descriere scurtă
lscpu afișează informații despre CPU
free afișează informații despre memoria sistemului
lshw afișează informații despre toate componentele hardware
dd copiază conținutul fișierelor și oferă control asupra acestui proces
lsmod afișează starea unui modul în cadrul kernelului Linux
modinfo afișează informații despre un modul
systemctl controlează sistemul systemd
crontab gestionează fișierele crontab

Obiective

  • Prezentarea specificațiilor componentelor hardware
  • Familiarizarea cu dispozitivele fizice și virtuale
  • Familiarizarea cu driverele disponibile în sistem
  • Ințelegerea conceptelor legate de serviciile ce rulează în cadrul unui sistem
  • Obținerea de abilități de lucru cu servicii

Folosire Git pentru laborator

Pe parcursul laboratoarelor, pentru descărcarea fișierelor necesare laboratorului, vom folosi Git. Git este un sistem de controlul versiunii și e folosit pentru versionarea codului în proiectele software mari. Celor interesați să aprofundeze conceptele din spatele comenzii git, precum și utilizări avansate, le recomandăm cursul practic online de pe gitimmersion.

Informațiile despre laboratorul de USO se găsesc în acest repository Git.

Pentru a pregăti infrastructura de laborator rulați comenzile de mai jos într-un terminal. Deschideți un terminal folosind combinația de taste Ctrl+Alt+t. În listarea de mai jos student@uso:~$ este promptul unde introduceți comenzile, pe acela nu-l tastați.

student@uso:~$ cd ~
student@uso:~$ git clone https://github.com/systems-cs-pub-ro/uso-lab.git

Cam atât cu pregătirea laboratorului. Acum haideți să ne apucăm de treabă! :-)

Concepte

Informații despre hardware

Termenul hardware în contextul calculatoarelor se referă la părțile fizice ale unui calculator și la componentele care interacționează cu acesta. Prin dispozitive hardware interne ne referim la procesor/CPU, dispozitive de stocare masivă a datelor HDD/SSD, memorie, placă de bază, procesor grafic/GPU, etc. Dispozitivele hardware externe includ monitoarele, tastaturile, imprimantele, etc.

Hardware-ul, în general, cuprinde acele componente fizice (care pot fi atinse) ale unui calculator.

Cunoașterea elementelor hardware este esențială întrucât programele software interacționează în mod direct sau indirect (biblioteci, alte procese, etc.) cu acestea.

Din punctul de vedere al utilizatorului, cunoașterea elementelor hardware este utilă în multiple situații: achiziționărea unui sistem nou, instalarea unui sistem de operare, alegerea driverelor compatibile, optimizarea diferitelor aplicații sau în cazul depanării în situația în care apar probleme.

Dispozitive

În cadrul calculatoarelor ce rulează un sistem de operare bazat pe nucleul Linux, interacțiunea cu dispozitivele se poate face folosind fișierele care se află la în calea absolută /dev/. Fiind vorba de fișiere, utilizatorul poate să facă aceleași operații ca în cazul oricăror fișiere. La calea /dev/ identificăm două tipuri de dispozitive:

  • Fizice - au un corespondent hardware
  • Virtuale - nu au un corespondent hardware și sunte create de sistemul de operare.

Un exemplu de dispozitiv fizic este discul. Acesta începe cu litere sd sau hd în funcție de inferfața de conectare. Vom lista toate discurile prezente pe o mașină dată:

$ ls -l /dev/sd*
brw-rw---- 1 root disk 8,  0 Jul  9 02:23 /dev/sda
brw-rw---- 1 root disk 8,  1 Jul  9 02:23 /dev/sda1
brw-rw---- 1 root disk 8,  2 Jul  9 02:23 /dev/sda2
brw-rw---- 1 root disk 8,  3 Jul  9 02:23 /dev/sda3
brw-rw---- 1 root disk 8, 16 Jul  9 02:23 /dev/sdb
brw-rw---- 1 root disk 8, 17 Jul  9 02:23 /dev/sdb1

Pe lângă dispozitivele fizice, în cadrul sistemului Linux, avem și dispozitive virtuale cum ar fi:

  • /dev/zero - la citire generează zero-uri
  • /dev/random - la citire generaza numere aleatorii
  • /dev/null - la scriere, preia toate caracterele și le șterge (un coș de gunoi)

Servicii

După ce nucleul și driverele sunt încărcate și sistemul de operare este inițializat, trebuie pornite primele procese. Primul proces pornit este init, care apoi pornește la rândul său alte procese. Astfel, init este în vârful ierarhiei proceselor având rolul de a crea primele procese în cadrul sistemului de operare și adoptarea proceselor orfane.

Implementarea procesului init cea mai răspândită, la acest moment este systemd, cu executabilul aflat în /lib/systemd/systemd/:

student@uso:~$ ls -l /sbin/init
lrwxrwxrwx 1 root root 20 Aug  6 17:34 /sbin/init -> /lib/systemd/systemd

Serviciile sunt procese dedicate, de obicei procese daemon, care oferă funcționalități suplimentare sistemului sau care gestionează buna funcționare a acestuia. Serviciile sunt în general pornite și gestionate de systemd.

În systemd, serviciile, împreună cu alte componente, sunt numite units. Pentru a vizualiza toate unitățile systemd, folosimd comanda:

systemctl   list-unit-files --type=service

Dacă dorim adăugarea de servicii în sistem putem să le adăugăm în configurarea systemd. Acest proces va fi detaliat ulterior în secțiunea Nice to know.

Un proces daemon (sau simplu daemon) este un proces ce rulează în background. În mod tradițional, numele proceselor de tip daemon se termină cu litera d pentru a clarifica scopul lor și de a le diferenția față de celelalte procese din cadrul sistemului de operare. Mai multe detalii aici.

Demo

Informații despre procesor și arhitectură (lscpu, arch)

Cunoașterea informațiilor despre arhitectura unui procesor ne poate ajuta să scriem programe mai eficiente. De exemplu, pentru a afișa informații despre procesor putem să folosim comanda lscpu.

student@uso:~$ lscpu
Architecture:        x86_64
CPU op-mode(s):      32-bit, 64-bit
Byte Order:          Little Endian
CPU(s):              1
On-line CPU(s) list: 0
Thread(s) per core:  1
Core(s) per socket:  1
Socket(s):           1
NUMA node(s):        1
Vendor ID:           GenuineIntel
CPU family:          6
Model:               158
Model name:          Intel(R) Core(TM) i9-8950HK CPU @ 2.90GHz
Stepping:            10
CPU MHz:             2903.998
BogoMIPS:            5807.99
Hypervisor vendor:   KVM
Virtualization type: full
L1d cache:           32K
L1i cache:           32K
L2 cache:            256K
L3 cache:            12288K
NUMA node0 CPU(s):   0
Flags:               fpu vme de pse tsc msr pae mce cx8 apic sep mtrr pge mca cmov pat pse36 clflush mmx fxsr sse sse2 syscall nx rdtscp lm constant_tsc rep_good nopl xtopology nonstop_tsc cpuid pni pclmulqdq monitor ssse3 cx16 pcid sse4_1 sse4_2 x2apic movbe popcnt aes xsave avx rdrand hypervisor lahf_lm abm 3dnowprefetch invpcid_single pti fsgsbase avx2 invpcid rdseed clflushopt flush_l1d

Dacă dorim să aflăm informații ce țin doar de arhitectura sistemului curent, putem să folosim comanda arch:

student@uso:~$ arch
x86_64

Alternativ, putem afla detalii despre procesor folosind sistemul de fișiere procfs. Pentru mai multe detalii puteți accesa pagina de Wikipedia.

student@uso:~$ cat /proc/cpuinfo 
processor	: 0
vendor_id	: GenuineIntel
cpu family	: 6
model		: 158
model name	: Intel(R) Core(TM) i9-8950HK CPU @ 2.90GHz
stepping	: 10
cpu MHz		: 2903.998
cache size	: 12288 KB
[...]

Listare dispozitive disponibile în sistem

/dev reprezintă locația unde se află fișiere ce aparțin unor dispozitive. Navigând prin această ierarhie putem observa infomații despre dispozitive de stocare externe (/dev/sdX), dispozitive de tip joystick /dev/jsN , dar și despre dispozitive virtuale /dev/zero, /dev/random, /dev/urandom.

Această ierarhie variază de la un sistem la altul și reflectă starea curentă a acestuia.

student@uso:~$ ls -l /dev/
total 0
crw-rw-rw- 1 root root 1, 3 oct 15 16:10 /dev/null
[...]
brw-rw---- 1 root disk 8, 0 oct 15 16:10 /dev/sda
brw-rw---- 1 root disk 8, 1 oct 15 16:10 /dev/sda1
[...]
crw-rw-rw- 1 root root 1, 8 oct 15 16:10 /dev/random
[...]
crw-rw-rw- 1 root root 1, 9 oct 15 16:10 /dev/urandom
[...]
crw-rw-rw- 1 root root 1, 5 oct 15 16:10 /dev/zero

Informații despre toate componentele hardware (lshw)

Până acum am învațat că putem afla informații despre componenta hardware a sistemului folosind una dintre comenzile lscpu (informații despre procesor), free (informații despre memoria sistemului) sau inspectând fișierele din cadrul procfs.

Alternativ, pentru a afla informații despre componenta hardware a sistemului putem folosi comanda lshw.

student@uso:~$ sudo lshw
[sudo] password for student:
uso
    description: Computer
    product: VirtualBox
    vendor: innotek GmbH
    version: 1.2
    serial: 0
    width: 64 bits
    capabilities: smbios-2.5 dmi-2.5 vsyscall32
    configuration: family=Virtual Machine uuid=9FDEC515-C96C-47D8-AC70-C6BB8619EF02
  *-core
       description: Motherboard
       product: VirtualBox
       vendor: Oracle Corporation
       physical id: 0
       version: 1.2
       serial: 0
     *-firmware
          description: BIOS
          vendor: innotek GmbH
          physical id: 0
          version: VirtualBox
          date: 12/01/2006
          size: 128KiB
          capabilities: isa pci cdboot bootselect int9keyboard int10video acpi
     *-memory
          description: System memory
          physical id: 1
          size: 1993MiB
[...]

Generarea de fișiere de dimensiune fixă folosind dispozitive virtuale (dd)

Generarea unor fișiere de dimensiune fixă consituie primul pas în construirea unui fișier de tip imagine (.iso/.img). De asemenea putem șterge urme de informație rămasă pe suportul secund de stocare (HDD/SSD) prin umplerea zonelor libere cu zero-uri sau cu informație cu caracter aleator.

În Linux putem folosi dispozitivele virtuale (ex. /dev/urandom, /dev/random, /dev/zero) pentru a genera conținutul unui fișer nou.

student@uso:~$ cat /dev/urandom > dump
^C          # am oprit procesul corespunzător comenzii de mai sus trimițând semnalul SIGINT (Ctrl + c)
student@uso:~$ ls -lh dump
-rw-r--r-- 1 student student 281M oct 21 13:48 dump

Comenzile de mai sus au avut ca efect generarea unui fișier cu conținut aleator (vezi /dev/urandom), însă nu am putut controla dimensiunea noului fișier generat. Dimensiune fișierului generat variază în funcție de mai mulți parametri (ex. timp, viteză de scriere hdd/ssd, etc.).

Pentru a combate acest neajuns putem să folosim comanda dd. dd poate primi un fișier de intrare (if=<FILE>) și un fișier de ieșire (of=<FILE>). De asemenea putem să controlăm dimensiunea fișierului pe care vrem să îl generăm cu ajutorul parametrilor bs=<BYTES> și count=<BLOCKS>.

  • if - input file; dacă nu este specificat, se va folosi stdin;
  • of - output file; dacă nu este specificat, se va folosi stdout;
  • count - numărul de blocuri din fișierul input ce vor fi copiate;
  • bs - numărul de octeți dintr-un bloc.

Următorul apel al comenzii dd va umple fișierul dump cu 100MB (1024M * 100) de informație, conținând numai octeți cu valoarea zero.

student@uso:~$ dd if=/dev/zero of=dump bs=1M count=100
100+0 records in
100+0 records out
104857600 bytes (105 MB, 100 MiB) copied, 0,0832762 s, 1,3 GB/s
student@uso:~$ ls -lh dump 
-rw-r--r-- 1 student student 100M oct 21 14:07 dump

Similar, putem să generăm un fișier cu 32MB de informație cu caracter aleator:

student@uso:~$ dd if=/dev/urandom of=~/myfile.bin bs=4M count=8

Comanda dd mai poate fi folosită și pentru a obține backup-uri ale sistemului sau pentru a formata un sistem de fișiere virtual.

Generarea de șiruri aleatoare folosind dispozitive virtuale

Dispozitivele virtuale pot fi folosite pentru a genera parole aleatoare de dimensiune fixă.

student@uso:~$ head /dev/urandom | tr -dc A-Za-z0-9 | head -c 13 ; echo ''
P2wVwebdFFfcd

Identificare driverelor

În lumea Linux, implementarea unui driver (device driver) se face sub forma unei entități cu denumire de modul. Un modul oferă posibilitatea de a adăuga diferite funcționalități peste kernelul unui OS (ex. folosirea unui dispozitiv specializat pentru calcul grafic - GPU). Astfel, un driver rulează în cadrul unui kernel, având acces la modul privilegiat și putând fi încărcat la cerere.

Pentru a identifica toate modulele din cadrul unui kernel Linux putem să folosim comanda lsmod.

student@uso:~$ lsmod
Module                  Size  Used by
btrfs                1126400  0
zstd_compress         163840  1 btrfs
xor                    24576  1 btrfs
raid6_pq              114688  1 btrfs
ufs                    77824  0
qnx4                   16384  0
hfsplus               106496  0
hfs                    57344  0
minix                  32768  0
[...]

Pentru a afla mai multe informații despre un modul putem folosi comanda modinfo

student@uso:~$ modinfo ip_tables
filename:       /lib/modules/4.15.0-34-generic/kernel/net/ipv4/netfilter/ip_tables.ko
description:    IPv4 packet filter
author:         Netfilter Core Team <coreteam@netfilter.org>
license:        GPL
srcversion:     E73E003BA6D5C96B0DD463D
depends:        x_tables
retpoline:      Y
intree:         Y
name:           ip_tables
vermagic:       4.15.0-34-generic SMP mod_unload 
signat:         PKCS#7
signer:         
sig_key:        
sig_hashalgo:   md4

Recapitulare

În cadrul laboratorului curent vom folosi o serie de comenzi care au scopul de a afișa diferite informații despre sistemul curent. De cele mai multe ori, vom dori să extragem doar o parte din aceste infromații. Pentru a îndeplini această sarcină, va fi nevoie să apelăm la conceptele învațate în cadrul laboratoarelor anterioare.

Exerciții

  1. Afișați liniile 4:6 ale fișierelului /etc/passwd.
  2. Salvați conținutul directoarelor în mod recursiv, pornind de la directorul home al utilizatorului student într-un fișier denumit dump la calea /tmp/.
  3. Identificați liniile din fișierul /etc/shadow care conțin șirul de caractere student.

Basics

Prima parte a laboratorului își propune să urmărească identificarea componentelor hardware și atributelor acestora pentru un sistem existent.

Înainte de a începe exercițiile nu uitați să rulați comenzile de Git din secțiunea Folosire Git pentru laborator.

Informații despre procesor și arhitectură (lscpu, arch)

Exerciții

  1. Să se afișeze doar frecvența procesorului curent folosind comanda lscpu. (hint: grep)
  2. Să se afișeze doar arhitectura procesorului curent folosind comanda lscpu. (hint: grep)
  3. Să se afișeze numele modelului procesorului curent folosind procfs (/proc).

Informații despre starea curentă a memoriei (free)

Exerciții

  1. Să se afișeze informații despre memoria liberă în format human-readable. (hint: man free)

Listare dispozitive disponibile în sistem

Exerciții

  1. Afișați conținutul fișierului /dev/urandom în terminal.
  2. Afișați conținutul fișierului /dev/zero folosind comanda cat. Ce observați?
  3. Afișați, pe rând, conținutul fișierelor de mai sus în format hexazecimal. (hint: xxd sau hexdump)

Need to know

Informații despre toate componentele hardware

Exerciții

  1. Afișați pe rând următoarele atribute ale interfeței de rețea: product, vendor, capacity folosind comanda lshw. Hint: class
  2. Scrieți un script bash care culege următoarele informații despre hardware-ul curent:
    • informații despre procesor (model, frecvență)
    • versiunea de sistem de operare/distribuție (hint: uname)
    • versiune de kernel (hint: uname)
    • pachete instalate (hint: dpkg-query)

Generarea de fișiere de dimensiune fixă folosind dispozitive virtuale (dd)

Exerciții

  1. Să se genereze un fișier de dimensiune fixă (42 MB) care să conțină octeți aleatori. Dimensiunea unui block în cadrul comenzii dd trebuie să fie de 512 KB.
  2. Să se genereze un fișier de dimensiune fixă (54 MB) care să conțină octeți aleatori. Numărul de block-uri în cadrul comenzii dd trebuie să fie egal cu 216.

Generarea de șiruri aleatoare folosind dispozitive virtuale

Am văzut, în cadrul exercițiilor anterioare, că putem genera octeți aleatori prin simpla citire a fișierului /dev/urandom. Pentru a filtra caracterele nedorite am folosit comanda tr (translate) alături de -dc (d - delete, c - complement). Comanda echo de la final are rolul de a afișa caracterul newline - \n. Atenție: Comanda echo nu parsează ce primeste la stdin.

Exerciții

  1. Generați o parolă formată doar din cifre cu lungimea de 14 caractere folosind dispozitivele virtuale.

Identificarea driverelor

Exerciții

  1. Identificați un modul ce implementează o interfață de rețea și afișați detalii despre acesta. (hint: e1000).

Identificarea serviciilor ce rulează într-un sistem

student@uso:~$ systemctl list-unit-files --type=service
UNIT FILE                                  STATE
accounts-daemon.service                    enabled
acpid.service                              disabled
alsa-restore.service                       static
alsa-state.service                         static
alsa-utils.service                         masked
anacron.service                            enabled
apache-htcacheclean.service                disabled
apache-htcacheclean@.service               disabled
apache2.service                            enabled
[...]

Pentru a afla statusul unui serviciu putem folosi:

student@uso:~$ systemctl status dbus.service
● dbus.service - D-Bus System Message Bus
   Loaded: loaded (/lib/systemd/system/dbus.service; static; vendor preset: enabled)
   Active: active (running) since Mon 2018-10-15 16:10:25 EEST; 6 days ago
     Docs: man:dbus-daemon(1)
 Main PID: 555 (dbus-daemon)
    Tasks: 1 (limit: 2321)
   CGroup: /system.slice/dbus.service
           └─555 /usr/bin/dbus-daemon --system --address=systemd: --nofork --nopidfile --systemd-activation --syslog-only

Exerciții

  1. Afișați statusul serviciului networking.service
  2. Afișați toate serviciile care se află în starea enabled folosind doar comanda systemctl (hint: - -state)

Nice to know

Înainte de a începe această secțiune trebuie să vă asigurați că sunteți în directorul potrivit. Rulați comanda cd ~/uso-lab/07-inspect/support/nice-to-know.

Adăugarea unui serviciu nou în sistem

În secțiunea anterioară am învățat cum să afișăm serviciile disponibile în sistem și să aflăm statusul curent al unui serviciu.

Ne propunem să adugăm un serviciu nou în cadrul sistemului. Vom folosi un fișier de configurare deja existent.

student@uso:~/.../07-inspect/support/nice-to-know$ cat example.service
[Unit]
Description=example
 
[Service]
ExecStart=/usr/bin/python3 -m http.server 8000
Type=simple
User=student
Group=student
WorkingDirectory=/home/student
 
[Install]
WantedBy=multi-user.target

Fișierul de configurare de mai sus este organizat în secțiuni: [Unit], [Service], [Install].

  • Description - Descrierea componentei
  • ExecStart - Comanda Bash care pornește serviciul
  • Type - tipul serviciului, mai multe detalii aici
  • User - utilizatorul asociat cu procesul serviciului
  • Group - grupul asociat cu procesul serviciului
  • WorkingDirectory - directorul asociat cu procesul serviciului
  • WantedBy - este folosit pentru a specifica dacă serviciul este sau nu activat.

Mai multe informații se pot găsi aici.

Copiem fișierul de configurare în directorul /etc/systemd/system/ și adăugăm permisiunea de execuție.

student@uso:~/.../07-inspect/support/nice-to-know$ sudo cp example.service /etc/systemd/system/
student@uso:~/.../07-inspect/support/nice-to-know$ sudo chmod +x /etc/systemd/system/example.service

Activăm și pornim serviciul:

student@uso:~/.../07-inspect/support/nice-to-know$ sudo systemctl enable example
student@uso:~/.../07-inspect/support/nice-to-know$ sudo systemctl start example

Pentru a testa funcționalitatea serviciului, deschidem un browser și navigăm la URL-ul http://localhost:8000. Observăm că am pornit un server web care afișează conținutul directorului /home/student.

Pentru a pune în aplicare orice modificare adusă fișierului de configurare trebuie rulată următoarea comandă.

    student@uso:~$ sudo systemctl daemon-reload

Exerciții

  1. Scrieți un script Bash care construiește o arhivă .zip cu conținutul directorului /home/student/uso-lab/.
  2. Adăugați în sistem utilizatorul backupuso. Asigurați-vă că acest utilizator nu are un director home asociat.
  3. Construiți un serviciu care face backup directorului /home/student/uso-lab/ la fiecare două minute (citiți secțiunea următoare). Serviciul trebuie să aiba asociat utilizatorul creat la punctul precedent.

Planificarea execuției unui program (cron)

Ne dorim să realizăm diferite acțiuni în cadrul sistemului la anumite momente de timp. Pentru a rezolva această problemă putem să folosim daemonul cron.

student@uso:~$ ps -f $(pidof cron)
UID        PID  PPID  C STIME TTY      STAT   TIME CMD
root       563     1  0 07:47 ?        Ss     0:00 /usr/sbin/cron -f

Daemonul cron poate executa, la anumite momente de timp, programe sau script-uri configurate de un utilizator. Pentru configurarea listelor folosite de daemonul cron vom folosi utilitarul crontab.

Sintaxa folosită pentru a configura daemon-ul cron urmărește următoarea sintaxă:

Pentru a configura un program să se execute la un anumit interval de timp folosim comanda:

student@uso:~$ crontab -e

Dacă vrem să adăugăm data curentă la sfârșitul fișierului date_logs.log din directorul /var/log/ la fiecare 5 minute, introducem următoarea linie:

*/5   *   *   *   *   date >> /var/log/date_logs.log

Exerciții

  1. Adăugați un job cron care să înregistreze la fiecare două minute numărul de procese executate de utilizatorul student. Fiecare execuție, va adăuga un număr la sfârșitul fișierului n_processes.log din directorul /var/log/.
  2. Construți un script care identifică toate job-urile cron rulate de toți utilizatorii din sistem. Pentru fiecare job se vor afișa: numele utilizatorului și comanda rulată.

Get a life

1. Instalare shell nou

Să se instaleze un shell-ul zsh alături de Oh My Zsh în sistem. Faceți modificările necesare pentru a configura zsh ca shell prestabilit.

Găsiți aici repository-ul de Git pentru Oh My Zsh.

Hint: chsh.

2. Masină virtuală ARM

Masina virtuală este disponibilă aici. Folosiți scriptul start_machine.sh pentru a porni mașina virtuală.

Pentru login folosiți următoarele credențiale:

  • utilizator: root
  • parolă: 123456

După ce ați pornit mașina virtuală, vă puteți conecta la aceasta folosind următoarea comandă:

ssh -p 2222 root@localhost

  Asigurați-vă că aveți următoarele pachete instalate:
  sudo apt-get install qemu qemu-kvm qemu-system-arm

3. Stick bootabil UDPCast Creați un stick bootabil cu UDPCast urmărind acest link.

4. Stick bootabil cu distribuție de Linux Folosiți următoarele utilitare:

  • dd - pentru ștergerea tabelei de partiții
  • gparted - pentru crearea partițiilor noi
  • UNetbootin - pentru crearea Live USB drive

Sumar. Cuvinte cheie

  • Vizualizarea componentelor hardware: lshw, lscpu
  • Dispozitive în cadrul unui sistem: /dev
  • Dispozitive virtuale: /dev/random, /dev/urandom, /dev/zero
  • Lucrul cu systemd: systemctl
  • Vizualizare informații despre drivere/module: lsmod, modinfo
uso/laboratoare/new/07-inspect/all.1573392157.txt.gz · Last modified: 2019/11/10 15:22 by adrian.zatreanu
CC Attribution-Share Alike 3.0 Unported
www.chimeric.de Valid CSS Driven by DokuWiki do yourself a favour and use a real browser - get firefox!! Recent changes RSS feed Valid XHTML 1.0