Bài 03 · Phụ lục · đọc thêm, không bắt buộc

Lịch sử hệ điều hành: từ chạy theo lô tới Linux

Bài 03 nói rằng mọi chương trình phải xin kernel làm hộ, và "app" là một process. Phụ lục này kể lại vì sao máy tính được xây như vậy: năm bước nối tiếp nhau, mỗi bước giải quyết đúng vấn đề mà bước trước để lại.

Phụ lục · Lịch sử hệ điều hành Bài giảng 03

Phụ lục trả lời một câu hỏi mà Bài 03 để ngỏ. Vì sao giữa chương trình và phần cứng lại có một lớp trung gian là kernel, và vì sao chương trình đang chạy lại được gói thành một process? Không ai thiết kế những khái niệm đó từ đầu. Mỗi khái niệm là lời giải cho một vấn đề cụ thể, và vấn đề đầu tiên là chuyện tiền.

Trước khi có hệ điều hành: máy chờ người

Máy tính đầu tiên có hệ điều hành không? Không. Những máy tính điện tử thương mại đầu tiên xuất hiện vào thập niên 1950, đắt tới mức chỉ viện nghiên cứu, cơ quan nhà nước và công ty lớn mua nổi, và chúng không có hệ điều hành. Người dùng đăng ký một khung giờ, tự mang chương trình tới, tự nạp vào máy, chạy, lấy kết quả, rồi nhường máy cho người sau.

Chương trình thường được đưa vào máy dưới dạng thẻ đục lỗ (punched card), là những tấm thẻ giấy cứng. Mỗi thẻ thường chứa một dòng lệnh, mã hoá bằng vị trí các lỗ đục.

Cách làm này phí ở đâu? Nó phí đúng thứ đắt nhất. Trong lúc con người thao tác, tức đặt bộ thẻ, lắp cuộn băng, gạt công tắc, đọc kết quả, thì CPU không làm gì cả. Vì vậy vấn đề đầu tiên mà hệ điều hành ra đời để giải quyết là một vấn đề kinh tế. Thời gian của máy đắt hơn thời gian của người, nên phải bỏ con người ra khỏi khoảng trống giữa hai chương trình.

Chạy theo lô và chương trình giám sát

Batch processing xử lý theo lô
Gom công việc (job) của nhiều người thành một lô và cho máy chạy liên tiếp từ job này sang job khác, không cần người can thiệp giữa hai job.
Resident monitor chương trình giám sát thường trú
Một chương trình nằm thường trực trong bộ nhớ, đọc job tiếp theo, nạp và chạy nó, rồi lấy lại quyền điều khiển khi job kết thúc.

Chạy theo lô thì diễn ra thế nào? Người lập trình nộp bộ thẻ cho nhân viên vận hành. Bộ thẻ của nhiều người được xếp nối nhau, và thường được chép trước lên băng từ để máy đọc nhanh hơn. Sau đó chương trình giám sát lần lượt nạp và chạy từng job. Khi một job kết thúc, hoặc chạy quá thời gian cho phép, quyền điều khiển quay về chương trình giám sát, và nó nạp job tiếp theo.

Đây là dạng sơ khai đầu tiên của thứ Bài 03 gọi là kernel, tức một chương trình đứng giữa máy và chương trình của người dùng, quyết định chương trình nào được chạy tiếp theo.

Ảnh đen trắng một phòng máy: dãy tủ ổ băng từ có cuộn băng tròn, một bàn điều khiển, và năm người đứng trò chuyện.
ibm 7090 máy tính lớn
Một chồng thẻ đục lỗ dày được buộc bằng dây chun, mặt trên có chữ viết tay.
bộ thẻ một chương trình
Ảnh 1 Đơn vị làm việc của thời chạy theo lô. Bên trái: một máy IBM 7090 lúc lắp đặt ở trung tâm nghiên cứu Ames của NASA. Các tủ đứng có cuộn băng là ổ băng từ. Bên phải: một chương trình viết bằng ngôn ngữ PL/I cho máy IBM 360, đục trên thẻ khoảng năm 1969, theo mô tả của chính người làm ra nó. Người lập trình trao tay một chồng thẻ, không ngồi trước máy.

Giới hạn: CPU vẫn chờ, người vẫn chờ

Chạy theo lô giải quyết được khoảng trống giữa hai job, nhưng để lại hai khoảng trống khác. Khoảng trống thứ nhất là mỗi lúc chỉ có một job trong bộ nhớ, nên khi job đó phải chờ băng từ hay máy in thì CPU lại ngồi không, vì mấy thiết bị cơ khí đó chậm hơn CPU nhiều bậc.

Khoảng trống thứ hai là người lập trình phải chờ. Nộp bộ thẻ xong, thường phải đợi hàng giờ, có khi tới hôm sau, mới nhận được bản in kết quả, và gõ sai một dấu phẩy nghĩa là thêm một vòng chờ nữa. Cảnh chờ ấy về sau thành chuyện để kể, và để nói quá, như ở Ảnh 2.

Truyện sáu khung. Một người hỏi: What were things like in the old days? I hear you had to compile things for different processors? Người kia: Yeah. To compile your code, you had to mail it to IBM. Took 4-6 weeks. Before garbage collection, data would pile up until the computer got full and you had to throw it away. Early compilers could handle code fine, but comments had to be written in assembly. C could only be written on punch cards. You had to pick a compact font, or you'd only fit a few characters per card. C++ was big because it supported floppy disks. It still just punched holes in them, but it was a start. Người hỏi: Wow.
Ảnh 2 Truyện Old Days của xkcd, số 1755, năm 2016. Một người hỏi ngày xưa lập trình thế nào. Người được hỏi trả lời toàn chuyện bịa: muốn biên dịch phải gửi code qua bưu điện tới IBM và đợi 4 tới 6 tuần, ngôn ngữ C chỉ viết được trên thẻ đục lỗ. Người hỏi tin cả, vì cái nền là thật. Thời chạy theo lô, người lập trình đúng là nộp một chồng thẻ rồi chờ, có điều chờ hàng giờ chứ không phải hàng tuần.

Đa chương trình: giữ nhiều job trong bộ nhớ cùng lúc

Khoảng trống thứ nhất được lấp bằng gì? Bằng đa chương trình (multiprogramming), tức nạp nhiều job vào bộ nhớ cùng lúc. Khi job đang chạy phải chờ thiết bị thì chương trình giám sát chuyển CPU sang một job khác đang sẵn sàng. Hình 1 so sánh hai cách. CPU vẫn chỉ chạy một job tại một thời điểm, nhưng thời gian chờ của job này được lấp bằng công việc của job khác.

  Một job trong bộ nhớ (chạy theo lô)
    CPU   AAAA........AAAA........AAAA      . = CPU chờ băng từ, máy in

  Ba job trong bộ nhớ (đa chương trình)
    CPU   AAAABBBBCCCCAAAABBBBCCCCAAAA      A phải chờ thì B chạy
Hình 1 Cùng một khoảng thời gian, đa chương trình lấp chỗ CPU ngồi chờ bằng công việc của job khác.

Nhiều chương trình nằm chung một bộ nhớ thì có sinh chuyện không? Có, vì một job lỗi có thể ghi đè lên job khác, hoặc lên chính chương trình giám sát, và làm hỏng cả lô. Vì vậy phần cứng của các máy tính thời này dần có thêm hai cơ chế. Cơ chế thứ nhất là bảo vệ bộ nhớ, để mỗi job chỉ ghi được vào vùng của mình. Cơ chế thứ hai là chế độ đặc quyền của CPU, để chỉ chương trình giám sát mới được điều khiển thiết bị và đổi vùng bảo vệ.

Nối về Bài 03

Hai cơ chế đó chính là nền của tab Kernel. Kernel chạy ở chế độ đặc quyền của CPU, còn mọi chương trình khác ở user space và phải xin qua syscall. Mỗi process có vùng nhớ riêng, không đọc được vùng nhớ của process khác. Hai cơ chế ra đời vì nhiều job phải chia nhau một bộ nhớ.

Đa chương trình làm máy bận rộn, nhưng người lập trình vẫn chờ hàng giờ. Lời giải cho khoảng trống thứ hai ấy cũng chính là nơi khái niệm process thành hình.

Time-sharing: nhiều người dùng chung một máy, cùng lúc

Time-sharing chia sẻ thời gian
Cách vận hành trong đó thời gian CPU được cắt thành những lượt rất ngắn, luân phiên giữa chương trình của nhiều người dùng, nhanh tới mức mỗi người đều có cảm giác máy chỉ đang phục vụ mình.
Terminal thiết bị đầu cuối
Thiết bị chỉ dùng để gõ lệnh và nhận kết quả, nối với máy tính ở xa. Thời đó thường là một teletype: máy đánh chữ điện, in câu trả lời của máy tính ra giấy cuộn.
Một máy teletype đứng trên chân sắt: bàn phím, khoang in giấy cuộn phía trên, bộ đọc băng giấy bên trái.
Ảnh 3 Teletype Model 33 ASR sản xuất năm 1968, ở một bảo tàng máy tính. Loại máy này là terminal phổ biến của các hệ time-sharing. Chữ tty mà Linux vẫn dùng để gọi terminal là viết tắt của teletype.

Time-sharing khác đa chương trình ở đâu? Khác ở lúc nào CPU được chuyển. Đa chương trình chỉ chuyển khi job đang chạy phải chờ thiết bị, nên một job tính toán dài, không bao giờ chờ, thì giữ CPU tới khi xong. Time-sharing thì chuyển theo đồng hồ. Một bộ đếm giờ phần cứng định kỳ lấy quyền điều khiển khỏi chương trình đang chạy và trả về cho hệ điều hành, nên chương trình hết lượt bị tạm dừng dù chưa xong.

Nhờ vậy không ai giữ được CPU quá lâu, và người ngồi ở terminal gõ một lệnh thì nhận câu trả lời sau vài giây, thay cho vài giờ.

  terminal 1 ──┐
  terminal 2 ──┼──►  một máy tính  ──►  CPU:  1 2 3 1 2 3 1 2 3 ...
  terminal 3 ──┘                         hết lượt thì chuyển, dù chưa xong
Hình 2 Time-sharing: một CPU luân phiên giữa chương trình của nhiều người theo từng lượt ngắn do đồng hồ quyết định.

Một trong những hệ time-sharing đầu tiên là CTSS (Compatible Time-Sharing System) ở MIT, đầu thập niên 1960. Nó chạy trên một máy IBM 7094 đã được sửa đổi, là đời sau của chiếc 7090 ở Ảnh 1. Từ đó, ý tưởng một máy tính phục vụ nhiều người qua terminal trở thành hướng đi chính của các hệ điều hành.

Từ cắt lượt tới process

Cắt lượt đặt ra một yêu cầu mà chạy theo lô không có. Một chương trình bị dừng giữa chừng thì ở lượt sau phải chạy tiếp được đúng từ chỗ đó, như chưa từng bị dừng. Muốn vậy thì với mỗi chương trình đang chạy, hệ điều hành phải giữ lại một bộ trạng thái:

  • lệnh tiếp theo cần chạy;
  • các giá trị đang tính dở trong CPU;
  • vùng bộ nhớ của nó;
  • các file nó đang mở;
  • nó thuộc người dùng nào.

Chương trình đang chạy cùng bộ trạng thái đó được gọi là process. Danh sách trên gần như trùng khớp với Hình 6 của Bài 03, các thuộc tính riêng của một process. Vậy các thuộc tính đó tồn tại vì nhiều chương trình phải chia nhau một CPU mà không giẫm lên nhau.

Multics: tham vọng lớn, và Bell Labs rút lui

Giữa thập niên 1960, ba bên cùng làm Multics, gồm MIT, Bell Labs (phòng nghiên cứu của công ty điện thoại AT&T) và hãng General Electric. Multics là một hệ time-sharing rất tham vọng, nhắm tới việc phục vụ rất nhiều người cùng lúc như một tiện ích công cộng, và tham vọng đó kéo theo độ phức tạp.

Multics kết thúc ra sao với Bell Labs? Theo hồi ký của Dennis Ritchie, một người của Bell Labs trong dự án, tới khoảng 1968-1969 Multics vẫn chưa cho ra một hệ thống dùng được như mong đợi, nên Bell Labs dần rút khỏi dự án. Vài lập trình viên của Bell Labs vì thế mất một môi trường làm việc mà họ đã quen.

Nối về Bài 03

Bộ lập lịch của kernel trong tab Bốn tài nguyên kế thừa trực tiếp ý tưởng của time-sharing: CPU được chia theo lượt. Nên khi có quá nhiều process thì không ai bị huỷ, chỉ phải chờ lâu hơn tới lượt mình. Đó là lý do hết CPU thì app chậm chứ không chết.

Process trên server của bạn được sinh ra bằng fork() rồi execve(), và shell chỉ là một process như mọi process khác. Cả hai điều ấy bắt nguồn từ Unix, ở Bell Labs, năm 1969.

Unix (1969): một hệ thống nhỏ, xây quanh process và file

Ảnh đen trắng hai người đàn ông để râu: Ken Thompson bên trái, Dennis Ritchie bên phải.
Ảnh 4 Ken Thompson (trái) và Dennis Ritchie, hai người tạo ra Unix ở Bell Labs. Ảnh chụp năm 1973, năm kernel Unix được viết lại bằng C.

Mất Multics rồi thì họ làm gì? Ken Thompson tìm được một máy tính nhỏ loại PDP-7 ít người dùng, và bắt đầu viết trên đó một hệ thống nhỏ hơn Multics rất nhiều. Dennis Ritchie cùng làm, và Ritchie ghi năm ra đời của Unix là 1969.

Phiên bản đầu được viết bằng hợp ngữ (assembly), ngôn ngữ gần với lệnh máy và gắn chặt với một loại CPU cụ thể. Theo chính Ritchie, những thứ được dựng đầu tiên là hệ thống file và khái niệm process. Tiếp đó là một bộ công cụ nhỏ để chép, in, xoá, sửa file, và một trình thông dịch lệnh đơn giản gọi là shell.

fork() và exec()
Hai bước sinh process đã gặp ở tab Process của Bài 03: nhân bản process hiện tại thành process con, rồi process con thay chương trình của mình bằng chương trình khác. Trên Linux, bước thứ hai là syscall execve().
exit() và wait()
exit(): process tự kết thúc và để lại một mã kết thúc. wait(): process cha dừng lại chờ một process con kết thúc và nhận mã đó.

Shell chỉ là một process bình thường

Ritchie mô tả cách shell của Unix chạy một lệnh thành năm bước, và năm bước đó vẫn là cách bash chạy java -jar app.jar hôm nay. Hình 3 tóm lại theo lời Ritchie.

  1 shell đọc một dòng lệnh từ terminal
  2 shell gọi fork()        →  sinh process con, bản sao của shell
  3 process con gọi exec()  →  nạp chương trình của lệnh từ file
  4 shell gọi wait()        →  chờ process con kết thúc bằng exit()
  5 shell quay lại bước 1
Hình 3 Shell chạy một lệnh, tóm theo bài "The Evolution of the Unix Time-sharing System" của Dennis Ritchie.

Thiết kế này kéo theo hai điều. Một là shell không nằm trong kernel và không có đặc quyền gì. Nó chạy trong user space và dùng đúng những syscall mà mọi chương trình khác dùng, nên ai cũng có thể viết một shell khác mà không phải sửa kernel, và bash là một trong số đó.

Hai là mọi lệnh đều được sinh ra bằng fork() từ một process có sẵn, nên mọi process trên máy xếp thành một cây có gốc chung. Đó chính là cây ở Hình 5 của Bài 03, nơi java là con của bash.

1973: viết lại kernel bằng C

Viết bằng hợp ngữ thì Unix chỉ chạy được trên một loại máy, vậy sao nó lan ra được? Nhờ ngôn ngữ C mà Ritchie phát triển. C đủ gần phần cứng để viết hệ điều hành, nhưng không gắn với một loại CPU như hợp ngữ, và năm 1973 kernel Unix được viết lại bằng C. Ritchie gọi đó là bước ngoặt quan trọng nhất, vì từ đây hệ thống có hình hài hiện đại, và cũng là lúc Unix có đa chương trình. Về lâu dài, việc này cho tính khả chuyển: đưa Unix sang một loại máy khác chủ yếu là biên dịch lại code C, chỉ phải viết lại phần nhỏ phụ thuộc phần cứng.

Lan rộng rồi thì lại sinh chuyện gì? Unix sang tới các trường đại học và hãng máy tính, và mỗi nơi phát triển một nhánh riêng. Tới thập niên 1980 đã có nhiều bản Unix khác nhau, và một chương trình viết cho bản này chưa chắc chạy được trên bản kia.

Lời giải là một chuẩn chung tên là POSIX, bản đầu tiên công bố năm 1988. POSIX quy định giao diện mà một hệ kiểu Unix phải cung cấp, trong đó có fork(), exec, signal. Các man page ở Bài 03 vẫn ghi rõ mỗi syscall, mỗi signal thuộc phiên bản POSIX nào.

Nối về Bài 03

Ba thứ Bài 03 dùng hằng ngày đến thẳng từ Unix: process sinh ra bằng fork rồi exec, shell là một process trong user space, và mọi process xếp thành cây từ một gốc chung: PID 1.

Unix giải quyết bài toán kỹ thuật, nhưng để lại một bài toán khác: ai được dùng và sửa nó. Lời giải cho bài toán ấy chính là kernel đang chạy trên server của bạn.

Vấn đề cuối thập niên 1980: Unix không tự do, máy PC thì rẻ

Unix thuộc về ai? Nó là tài sản của AT&T, và các bản Unix thương mại về sau đều đi kèm giấy phép ràng buộc việc sao chép, sửa và chia sẻ mã nguồn. Trong khi đó, máy tính cá nhân dùng chip Intel 80386 đã đủ mạnh để chạy một hệ thống kiểu Unix, nhưng không có sẵn một hệ như vậy vừa rẻ vừa được tự do sửa.

Người đầu tiên tìm cách lấp chỗ trống đó là Richard Stallman. Từ năm 1983, ông công bố dự án GNU, nhằm viết lại từ đầu một hệ thống kiểu Unix mà ai cũng được dùng, sửa và chia sẻ. Tới đầu thập niên 1990, GNU đã có trình biên dịch, shell hoàn chỉnh.

Linux (1991): mảnh còn thiếu là một kernel

Thứ GNU còn thiếu là kernel, và nó tới từ đâu? Năm 1991, Linus Torvalds, khi đó là sinh viên ở Helsinki, viết một kernel cho máy PC dùng chip 80386 và công bố mã nguồn. Ghép kernel đó với các công cụ của GNU thì được một hệ thống hoàn chỉnh. Hình 4 vẽ lại sự ghép nối đó bằng chính sơ đồ phân tầng của Bài 03.

  user space   bash · trình biên dịch · công cụ dòng lệnh   ← dự án GNU (từ 1983)
               Nginx · java · PostgreSQL                   ← chương trình của bạn
                              │  syscall
  kernel       Linux                                       ← Linus Torvalds (1991)
                              ▼
               CPU    RAM    Disk    Network
Hình 4 Một hệ thống Linux là kernel Linux cộng phần user space mà phần lớn đến từ dự án GNU và các dự án khác.

Vậy "Linux" chỉ cái gì? Theo nghĩa hẹp, Linux chỉ là kernel, đúng thứ mà Bài 03 gọi là kernel. Thứ người ta cài lên server, như Ubuntu hay Debian, là một bản phân phối (distribution), tức kernel Linux cộng rất nhiều chương trình user space đã chọn sẵn và đóng gói cùng nhau. Linux đi theo các giao diện của POSIX, nên phần lớn chương trình và khái niệm của Unix, như process, fork rồi exec, signal, PID 1, chạy trên Linux gần như nguyên vẹn.

Nối về Bài 03

Server thật thì chạy Linux, và những gì Bài 03 kể về signal, OOM killer hay systemd đều là việc của kernel Linux. Đó cũng là lý do lab của Bài 03 trên Windows chỉ cho thấy những thứ tương đương.

Mỗi bước trên còn để lại dấu vết trên server hôm nay, ngay trong những lệnh bạn đã gõ ở Bài 03.

Những gì còn lại tới hôm nay

Cửa sổ terminal nền đen chữ trắng liệt kê tệp của Unix bản 7: các tệp hệ thống trong đó có init, sáu tệp kernel tên kết thúc bằng unix, và tệp /bin/sh.
Ảnh 5 Unix bản 7 (1979) chạy lại trong trình giả lập SIMH. Sáu file kernel, mỗi file khoảng 50 KB. Kernel là một file chương trình trên đĩa như mọi chương trình khác, chỉ khác ở chỗ được nạp đầu tiên và chạy ở chế độ đặc quyền. Shell /bin/sh, khoảng 17 KB, là một chương trình user space bình thường. init là chương trình chạy làm PID 1 của Unix. Trên nhiều bản Linux hôm nay, vai trò đó thuộc về systemd.

Ảnh 5 cho thấy cả hệ thống nhỏ tới mức nào, và cũng cho thấy những cái tên vẫn còn được dùng. Ngoài init và sh, nhiều dấu vết khác vẫn nằm ngay trong những lệnh bạn gõ ở Bài 03:

  • Con số 9 trong kill -9 là số hiệu của SIGKILL từ các bản Unix đời đầu, được Linux giữ nguyên.
  • Man page vẫn chia theo những phần đánh số của sổ tay Unix. Phần 2 là syscall, nên mới có fork(2), bind(2). Phần 7 là các trang tổng quan, nên có signal(7).
  • Chữ tty trong tên thiết bị terminal của Linux nhắc lại chiếc teletype ở Ảnh 3.
Bảng 1 Lịch sử theo từng vấn đề: mỗi vấn đề, lời giải của nó, và nơi lời giải đó xuất hiện trong Bài 03.
Vấn đềLời giảiTrong Bài 03
Máy đắt ngồi chờ người giữa hai chương trìnhChạy theo lô, chương trình giám sátKernel quyết định chương trình nào chạy
CPU ngồi chờ thiết bị; job lỗi ghi đè job khácĐa chương trình, bảo vệ bộ nhớ, chế độ đặc quyềnVùng nhớ riêng của process; kernel và user space; syscall
Người chờ kết quả hàng giờTime-sharing, cắt lượt theo đồng hồBộ lập lịch; hết CPU thì app chậm
Chương trình bị dừng phải chạy tiếp đượcGiữ trạng thái riêng cho mỗi chương trình đang chạyProcess và các thuộc tính riêng của nó
Hệ thống quá phức tạp (Multics)Unix: fork rồi exec, shell là process thườngCây process, bash sinh ra java
Nhiều bản Unix không tương thíchChuẩn POSIX (1988)Man page ghi phiên bản POSIX của syscall, signal
Không có hệ kiểu Unix tự do cho máy PCGNU (1983) và kernel Linux (1991)Kernel trên server của bạn

Nối về bài chính

Dấu vết còn lại tới nay

Kernel, process, syscall, bộ lập lịch, fork rồi exec: mỗi khái niệm của Bài 03 là lời giải cho một vấn đề có thật. Máy đắt thì phải bận. Nhiều chương trình phải chung một bộ nhớ mà không phá nhau. Nhiều người phải chung một CPU mà không ai chờ quá lâu.

Khi một app chết vì hết RAM ở tab Process chết, bạn đang thấy đúng cơ chế đã ra đời để giữ cho nhiều chương trình cùng sống trên một máy.

Quay lại bài giảng Bài 03.

Nguồn đọc thêm

  1. Dennis M. Ritchie, The Evolution of the Unix Time-sharing System. Hồi ký kỹ thuật của chính người làm ra Unix: Multics, PDP-7, năm bước của shell, việc viết lại bằng C.
  2. D. M. Ritchie, K. Thompson, The UNIX Time-Sharing System, Communications of the ACM, 1974. Bài báo đầu tiên mô tả Unix.
  3. Dennis M. Ritchie, The Development of the C Language. Vì sao C ra đời, và quan hệ của nó với Unix.
  4. Thông báo khởi động dự án GNU, năm 1983.
  5. The Open Group Base Specifications, bản hiện hành của chuẩn POSIX.
  6. fork(2), man page của syscall sinh process trên Linux, có mục ghi chuẩn và lịch sử.

Nguồn ảnh

Ảnh chụp thật đã được chuyển sang hai tông đen trắng và cắt khung cho khớp trang. Ảnh bộ thẻ và teletype được tách khỏi nền cũ. Ảnh chụp màn hình Unix bản 7 được cắt bớt nền trống, phóng to hai lần và vẽ thêm khung đánh dấu. Truyện tranh của xkcd dùng theo giấy phép phi thương mại ghi ở từng dòng, đã phóng to và chuyển sang đen trắng.

  1. Ảnh 1Máy IBM 7090, NASA Ames Research Center / Emerson Shaw, Wikimedia Commons, public domain.
  2. Ảnh 1Bộ thẻ đục lỗ của một chương trình, ArnoldReinhold, Wikimedia Commons, CC BY-SA 3.0.
  3. Ảnh 2Old Days, Randall Munroe, xkcd.com, CC BY-NC 2.5.
  4. Ảnh 3Teletype Model 33 ASR (1968), Dennis van Zuijlekom, Wikimedia Commons, CC BY-SA 2.0.
  5. Ảnh 4Ken Thompson và Dennis Ritchie, không rõ tác giả, Wikimedia Commons, public domain.
  6. Ảnh 5Unix bản 7 trong trình giả lập SIMH, Huihermit, Wikimedia Commons, CC0.