GIỚI THIỆU JACKOS
Giới thiệu JackOS
JackOS là hệ điều hành được phát triển bằng ngôn ngữ Jack, cung cấp nhiều dịch vụ quan trọng như toán học, quản lý bộ nhớ, đồ họa, in ký tự ra màn hình, xây dựng và xử lý chuỗi, cũng như đọc đầu vào từ bàn phím.
Hệ điều hành Jack được cấu trúc thành 8 lớp, cho phép triển khai và kiểm tra từng lớp một cách độc lập và theo thứ tự tùy chọn.
Mục tiêu
Hệ điều hành Jack được triển khai để dịch và thực thi các ngôn ngữ lập trình cấp cao, dựa trên đối tượng và nền tảng phần cứng máy tính đơn giản Việc kiểm tra hệ điều hành này được thực hiện thông qua các chương trình và thử nghiệm được mô tả chi tiết dưới đây.
Nắm vững và thực hành các chủ đề trong khoa học máy tính ứng dụng như xử lý ngăn xếp, phân tích cú pháp, tạo mã, và các thuật toán cổ điển, cùng với cấu trúc dữ liệu để quản lý bộ nhớ, đồ họa vector, và xử lý đầu vào - đầu ra là rất quan trọng Những kiến thức này nằm ở cốt lõi của mọi hệ thống máy tính hiện đại.
Yêu cầu cài đặt
JackOS is designed to operate in a Command-line Environment, such as a terminal or shell To install and use JackOS for project-related tasks, it is essential to set up the necessary tools.
Các công cụ phần mềm nand2tetris cung cấp các tệp Batch (cho Windows), các tập lệnh (cho Unix và Mac OS), Compiler Jack và VM Emulator.
Cài đặt Java Run-time Environment Để có hiệu suất tốt nhất, hãy tải xuống phiên bản mới nhất hiện có.
The API JackOS được triển khai hoàn thiện trong dự án này.
CÀI ĐẶT, BUILD, CHẠY VÀ DEBUG JACKOS
Cài đặt phần mềm Nand to Tetris
Bộ phần mềm Nand to Tetris cung cấp đầy đủ công cụ và tệp cần thiết để hoàn thành dự án, cho phép người dùng sử dụng tự do theo các điều khoản của Giấy phép Công cộng Chung GNU GPL Với mã nguồn mở, Nand2tetris bao gồm hai thư mục chính là project và tools.
Hình 2.1: Bộ phần mềm Nand to Tetris 2.1.1 Thư mục projects
Thư mục projects bao gồm 14 thư mục dự án được đánh số từ 00 đến 13, trong đó thư mục 12 chứa hệ điều hành JackOS Thư mục dự án 12 này lưu trữ các tệp cần sửa đổi và hoàn thiện cho dự án.
Có tám thư mục chính: Array, Keyboard, Math, Memory, Output, Screen, String và Sys Mỗi thư mục Xxx đi kèm với một tệp lớp Xxx.jack chứa tất cả các chữ ký cần thiết cho chương trình con, cùng với lớp thử nghiệm tương ứng mang tên Main.jack và các tập lệnh thử nghiệm liên quan.
Hình 2.4: Một số tệp lớn trong các thư mục khác nhau của Projects 12
Thư mục tools chứa các công cụ phần mềm, các tệp và thư mục:
Các tệp bat và sh là các tệp và tập lệnh, được sử dụng để gọi các công cụ phần mềm nand2tetris.
Hình 2.5: Các tệp trong thư mục tools
Thư mục chứa mã nguồn của các công cụ phần mềm nand2tetris bao gồm nhiều thư mục con, trong đó có các tệp lớp Java và tệp hỗ trợ cần thiết.
Thư mục builtInChips và thư mục builtInVMCode chứa các tệp được sử dụng bởi Trình mô phỏng phần cứng và Trình mô phỏng VM được cung cấp tương ứng.
Hình 2.7: Các tệp có trong thư mục builtInChips và builtInVMCode
Thư mục OS chứa phiên bản đã biên dịch của hệ điều hành Jack.
Hình 2.8: Các phiên bản trong thư mục OS
Công cụ Hardware Simulator mô phỏng và kiểm tra các cổng và chip logic được thực hiện trong HDL (Ngôn ngữ mô tả phần cứng).
Hình 2.9: Công cụ Hardware Simulator
Hình 2.10: Màn hình chính của công cụ Hardware Simulator
Công cụ CPU Emulator là một phần mềm mô phỏng hoạt động của hệ thống máy tính Hack, cho phép người dùng kiểm tra và thực thi các chương trình được viết bằng ngôn ngữ máy Hack Nó hỗ trợ cả phiên bản nhị phân lẫn hợp ngữ, giúp tối ưu hóa quá trình phát triển và kiểm tra ứng dụng.
Hình 2.12: Màn hình chính của công cụ Emulator
Công cụ VM Emulator mô phỏng hoạt động của máy ảo, tương tự như JVM trong Java, cho phép chạy và kiểm tra các chương trình viết bằng ngôn ngữ VM, giống như Bytecode trong Java.
Hình 2.13: Công cụ VM Emulator
Hình 2.14: Màn hình chính của công cụ VM Emulator
Công cụ Assembler chuyển đổi các chương trình viết bằng hợp ngữ Hack thành mã nhị phân Hack Mã nhị phân này có thể được thực thi trực tiếp trên chip máy tính trong phần mềm mô phỏng phần cứng hoặc được chạy trên CPU Emulator.
Công cụ Compiler chuyển đổi các chương trình viết bằng ngôn ngữ Jack thành mã VM, cho phép chạy trên VM Emulator Mã VM sau đó có thể được dịch sang mã lắp ráp VM và chuyển đổi thành Hack, để thực thi trên CPU Emulator.
Công cụ Text Comparer kiểm tra xem hai tệp văn bản đầu vào có giống hệt nhau hay không, cho đến chênh lệch khoảng trắng.
Xây dựng The API JackOS
Array là thuật ngữ dùng để chỉ mảng trong ngôn ngữ Jack, bao gồm các lớp array Để truy cập các mục trong mảng sau khi khai báo, bạn có thể sử dụng cú pháp thông thường arr[i] Mỗi mục trong mảng có thể chứa kiểu dữ liệu nguyên thủy hoặc bất kỳ kiểu đối tượng nào, cho phép các mục khác nhau trong mảng có kiểu dữ liệu đa dạng.
Bảng 2.1: Xây dựng code và công dụng của Array
Tên hàm Code Công dụng function
(int size) function Array new (int size) { return Memory.alloc (size);
Tạo một mảng mới với kích thước đã cho sẵn. method void dispose () method void dispose () { do Memory.deAlloc (this); return;}
Là thư viện dùng để xử lý dữ liệu nhập của người dùng từ bàn phím static int KEYBOARD_ADDRESS; static int LINE_BUFFER_LENGTH;
Bảng 2.2: Xây dựng code và công dụng của Keyboard
Tên hàm Code Công dụng function void init () function void init () { let KEYBOARD_ADDRESS = 24576; let LINE_BUFFER_LENGTH = 64; return;
Khởi tạo bàn phím. function char keyPressed () function char keyPressed () { return
Trả về ký tự của bàn phím hiện đang được nhấn trên bàn phím. function char readChar () function char readChar () { var char key; while (key = 0) { let key = Keyboard.keyPressed ();
} while (~(Keyboard.keyPressed () = 0)) {} return key;
Khi một phím trên bàn phím được nhấn và sau đó thả ra, hệ thống sẽ lặp lại quá trình này với màn hình và trả về ký tự tương ứng của phím đó Nếu không có phím nào được nhấn, kết quả sẽ là 0.
(String message) function String readLine (String message) { return Keyboard._readLine (message, false);
Hàm `readInt` hiển thị thông báo trên màn hình và đọc văn bản từ bàn phím cho đến khi phát hiện một dòng mới Sau đó, nó lặp lại văn bản ra màn hình và trả về giá trị của nó.
(String message) function int readInt (String message) { var String str; let str = Keyboard._readLine(message, true); while (str.length () = 0) { let str = Keyboard._readLine (message, true);
Chức năng hiển thị thông báo trên màn hình và đọc văn bản từ bàn phím cho đến khi phát hiện ký tự dòng mới Nó lặp lại văn bản ra màn hình và trả về giá trị nguyên cho đến khi gặp ký tự không phải chữ số đầu tiên trong văn bản đã nhập.
The function `String readLine(String message, boolean onlyDigits)` is designed to read user input It initializes a character variable `key` and a string variable `ret`, allocating memory for the input buffer The function prompts the user with the provided message and captures a character from the keyboard It continues to read input until a valid condition is met, ensuring that the input adheres to the specified criteria, particularly if the `onlyDigits` flag is set.
LINE_BUFFER_LENGTH))) { // intro if (key = 129) { // backspace do Output.backSpace (); do Output.printChar (32); do Output.backSpace ();
Chuỗi đọc dòng Thông báo chuỗi, chỉ cho các chữ số Boolean)
Xử lý không gian ngược của người dùng (vd: xác thực dấu (-) ở đầu, phân tách dấu chấm, dấu phẩy… chỉ hỗ trợBoolean). do ret.eraseLastChar ();
(key < 58))) { do ret.appendChar (key); do Output.printChar (key);} else {if ((ret.length() = 0) & (key = 45)){ do ret.appendChar (key); do Output.printChar (key);
} do Output.println (); return ret;
Thư viện các hàm toán học thường được sử dụng. static Array EYE;
Bảng 2.3: Xây dựng code và công dụng của Math
Tên hàm Code Công dụng function int multiply (int x, int y) function int multiply (int x, int y) { var int i, ret; let i = 0; let ret = 0; while(i < 16){ if(Math.getBit(y, i) = 1){ let ret = ret + x;
Trả về tích của x và y Khi một trình biên dịch Jack phát hiện toán tử nhân ‘*’ trong mã của chương trình, nó xử lý bằng cách let x = x + x; let i = i + 1;
This method demonstrates that the expressions "Jack x * y" and "Math.multiply(x, y)" yield the same value The function "int divide(int x, int y)" includes a variable "int q" and initializes "sign" to 1 It also checks for division by zero, returning an error if "y" equals 0.
} if(y < 0) { if(x > 0) { return -Math.divide(x, -y);
} } else { if(x < 0){ return -Math.divide(-x, y);
Khi trình biên dịch Jack gặp toán tử phân chia “/” trong mã chương trình, nó sẽ xử lý bằng cách gọi phương pháp tương ứng Điều này có nghĩa là các biểu thức Jack x/y và Math.divide(x, y) sẽ trả về cùng một giá trị Ví dụ, trong hàm int min(int x, int y), nếu x lớn hơn y, hàm sẽ trả về giá trị y.
Trả về giá trị nhỏ nhất của x và y. return x;
} } function int sqrt (int x) function int sqrt (int x) { var int start, end, mid, value; if(x < 0){ do Sys.error (4);
} let start = 0; let end = x; while(start < end){ if(start = (end - 1)) { return start;
} let mid = start + ((end - start)/2); let value = mid * mid; if(value = x){ return mid;
} else { if((value > x) | value < 0){ let end = mid;
Trả về phần nguyên của căn bậc 2 của x function int max (int x, int y) function int max (int x, int y) { if (x > y) { return x;
Trả về giá trị lớn nhất của x và y. function void init () function void init () { var int i; let EYE = Array.new(16); let EYE[0] = 1; let i = 1; while(i < 16){ let EYE[i] = EYE[i-1] + EYE[i-1]; let i = i + 1;
Tạo mảng động. function int abs (int x) function int abs (int x) { if (x < 0) { return -x;
Lấy giá trị tuyệt đối.
Thư viện này cung cấp hai dịch vụ chính: truy cập trực tiếp vào bộ nhớ chính của máy tính (RAM) và quản lý phân bổ cũng như tái chế các khối bộ nhớ, với các cấu trúc dữ liệu là static Array RAM và static Array HEAP.
Bảng 2.4: Xây dựng code và công dụng của Memory
Tên hàm Code Công dụng function void init () function void init () { let RAM = 0; let HEAP = 2048; let HEAP [0] = -1; let HEAP [1] = 16384 - 2048 - 2; return;
Trả về giá trị RAM tại địa chỉ đã cho. function int peek (int address) function int peek (int address) { return RAM [address];
Trả về giá trị RAM tại địa chỉ đã cho. function void poke (int address, int value) function void poke (int address, int value) { let RAM [address] = value; return;
Lưu giá trị RAM tại địa chỉ đã cho bằng giá trị đã cho. function
(int size) function Array alloc (int size) { var Array current, selected, block; var int currentSize, selectedSize; if (size < 0){ do Sys.error (5);
} let current = HEAP; let selected = -1; let selectedSize = HEAP [1] + 1; let currentSize = current [1]; while (current > -1) { if (~(currentSize < (size + 2))) { if (currentSize < selectedSize) { let selected = current; let selectedSize = currentSize;
Tìm một khối RAM có sẵn với kích thước đã cho và trả về tham chiếu đến địa chỉ cơ sở của nó.
} if (selected = -1) { do Sys.error (6);
} let selected [1] = selected [1] - size - 2; let block = selected + selected [1] + 1; let block [0] = -1; let block [1] = size; return block + 2;
(Array array) function void deAlloc (Array array) { var Array current, block; let block = array - 2; let current = HEAP; while (current [0] > 0) { let current = current [0];
Bỏ cấp phát đối tượng đã cho (ép kiểu một mảng) bằng cách tạo nó có sẵn để phân bổ trong tương lai.
Thư viện dùng để hiển thị các văn bản được xuất ra trên màn hình.
Màn hình hiển thị có 256 hàng với mỗi hàng chứa 512 pixel Thư viện output sử dụng phông chữ cố định, trong đó chiều cao của mỗi ký tự là 11 pixel (bao gồm 1 pixel cho khoảng cách giữa các dòng) và chiều rộng là 8 pixel (bao gồm 2 pixel cho khoảng cách giữa các ký tự).
Kết quả hiển thị bao gồm 23 hàng, mỗi hàng chứa 64 ký tự, với vị trí ký tự đầu tiên được xác định tại tọa độ (0,0) Các ký tự được trình bày dưới dạng hình vuông, cho phép biết ký tự tiếp theo sẽ được hiển thị Các biến tĩnh bao gồm: int stat0, int stat1, bool stat2, String stat3, int stat4, và các mảng charMaps, stat6.
Bảng 2.5: Xây dụng code và công dụng của Output
Tên hàm Code Công dụng function void moveCursor
(int i, int j) function void moveCursor (int i, int j) { if ((i < 0) | (i > 22) | (j < 0) | (j > 63)) { do Sys.error (20);
} let stat0 = j / 2; let stat1 = 32 + (i * 352) + stat0; let stat2 = (j = (stat0 * 2)); do Output.drawChar (32); return;
Dùng để di chuyển con trỏ đến cột j của hàng i và xóa ký tự hiển thị ở đó. function void printChar
(char c) function void printChar (char c) { if (c = String.newLine ()) { do Output.println ();
} else { if (c = String.backSpace ()) { do Output.backSpace ();
} else { do Output.drawChar (c); if (~stat2) { let stat0 = stat0 + 1; let stat1 = stat1 + 1;
} if (stat0 = 32) { do Output.println();
Hiển thị ký tự đã cho tại vị trí con trỏ và đưa con trỏ về phía trước cột.
(String s) function void printString (String s) { var int l0, l1; let l1 = s.length (); while (l0 < l1) { do Output.printChar (s.charAt (l0)); let l0 = l0 + 1;
Display the given string at the cursor's starting position by appropriately upgrading the pointer The function `void printInt(int i)` sets the integer value using `stat3.setInt(i)` and then outputs the string using `Output.printString(stat3)`.
The function `println()` displays a given integer at the cursor's starting position It initializes `stat0` to zero, calculates `stat1` by adding 352 to `stat0`, and checks if `stat1` equals 8128 If true, it sets `stat1` to 32.
Di chuyển con trỏ đến dòng tiếp theo. function void backSpace () function void backSpace () { if (stat2) { if (stat0 > 0) {
Di chuyển con trỏ trở lại một cột. let stat0 = stat0 - 1; let stat1 = stat1 - 1;
} else { let stat0 = 31; if (stat1 = 32) { let stat1 = 8128;
Lớp String đại diện cho chuỗi ký tự và cung cấp các phương thức để xây dựng, sắp xếp, cũng như thao tác với các ký tự trong chuỗi Các chức năng bao gồm việc lấy và đặt ký tự riêng lẻ, xóa ký tự cuối cùng, và nối thêm ký tự vào cuối chuỗi Lớp này bao gồm các trường như int _maxLength, int length, và mảng chars để quản lý chuỗi hiệu quả.
Bảng 2.6: Xây dựng code và công dụng của String
Tên hàm Code Công dụng constructor
(int maxLength) constructor String new (int maxLength) { if (maxLength < 0) { do Sys.error(14);
Tạo 1 chuỗi rỗng mới với độ dài tối đa là maxLength và if (maxLength > 0) { let chars = Array.new(maxLength);
} let _maxLenth = maxLength; return this;
} độ dài ban đầu là 0. method void dispose () method void dispose () { if(~(chars = 0)){ do chars.dispose();
} do Memory.deAlloc(this); return;
Loại bỏ chuỗi này. method int length () method int length () { return length;
Trả về độ dài hiện tại của chuỗi này. method char charAt (int j) method char charAt (int j) { if((j < 0) | (~(j < length))){ do Sys.error(15);
Trả về ký tự ở vị trí thứ j của chuỗi này. method void setCharAt (int j, char c) method void setCharAt (int j, char c) { if((j < 0) | (~(j < length))){ do Sys.error(16);
} Đặt ký tự ở vị trí thứ j của chuỗi này thành c. method String appendChar method String appendChar (char c) { if(_maxLength = length){
Nối c vào cuối chuỗi này và trả
} let chars[length] = c; let length = length + 1; return this;
} về chuỗi mới. method void eraseLastChar
() method void eraseLastChar () { if (length = 0) { do Sys.error(18);
Xóa ký tự cuối cùng khỏi chuỗi này. method int intValue () method int intValue () { var int ret, i, sign; while(i < length){ if(i = 0){ if(chars[i] = 45){ let sign = -1;
} else { let sign = 1; if((chars[i] > 47) & (chars[i] < 58)){ let ret = (10 * ret) + chars[i] - 48;
} } } else { if((chars[i] > 47) & (chars[i] < 58)){ let ret = (10 * ret) + chars[i] - 48;
Trả về giá trị nguyên của chuỗi, cho đến khi phát hiện ra
1 ký tự không phải chữ số.
} method void setInt (int value) method void setInt (int value) { var int divisor; let length = 0; if(value < 0){ do appendChar(45); // minus let value = -value;
} Đặt chuỗi này để chứa một biểu diễn của giá trị đã cho. function char backSpace () function char backSpace () { return 129;
Trả về ký tự xóa lùi function char doubleQuote
Trả về ký tự ngoặc kép (“). function char newLine () function char newLine () { return 128;
Trả về ký tự dòng mới
Một thư viện các dịch vụ hệ thống cơ bản. static int CYCLES_PER_MS;
Bảng 2.7: Xây dụng code và công dụng của Sys
The function `init()` initializes various system components by calling `Memory.init()`, `Math.init()`, `Keyboard.init()`, `Screen.init()`, and `Output.init()`, followed by executing the main program with `Main.main()`, and finally halting the system with `Sys.halt()` The variable `CYCLES_PER_MS` is set to 35000, which may be used for timing purposes within the function.
Thực hiện tất cả các lần khởi tạo theo yêu cầu của hệ điều hành. function void halt () function void halt () { while(true)
Tạm dừng thực hiện chương trình. function void wait (int duration) function void wait (int duration) { var int i; while (i < (CYCLES_PER_MS * duration)) { let i = i + 1;
The function `void error(int errorCode)` displays an error message by printing "ERR" followed by the specified error code, and then halts the system.
Hiển thị mã lỗi đã cho dưới dạng
"ERR", và tạm dừng việc thực hiện chương trình
* Màn hình vật lý Hack bao gồm 256 hàng (được lập chỉ mục 0 255, từ trên xuống dưới)
* Trong số 512 pixel – điểm ảnh, mỗi pixel (được lập chỉ mục 0 511, từ trái sang phải) Pixel trên cùng bên trái bật
* Màn hình được lập chỉ mục (0,0).) static Array stat0; static int screen; static bool stat2;
Bảng 2.8: Xây dựng code và công dụng của Screen
Biên Dịch, chạy và kiểm tra
Sau khi hoàn tất việc cài đặt các công cụ cần thiết, bạn có thể tiến hành phát triển, biên dịch và kiểm tra từng lớp của OSClass.jack một cách độc lập Hãy tuân theo quy trình hướng dẫn dưới đây để thực hiện.
Để bắt đầu, hãy đặt các OSClass.jack đã hoàn thiện trong cùng một thư mục với chương trình thử nghiệm được cung cấp, nhằm mục đích kiểm tra tính năng của chúng.
Bước 2: Sử dụng trình biên dịch Jack để biên dịch thư mục, điều này sẽ tạo ra tệp OSClass.jack cùng với các tệp lớp thử nghiệm Quá trình này sẽ tạo ra một tệp mới có tên OSClass.vm.
Bước 3: Tải thư mục vào VM Emulator được cung cấp.
Hình 2.18: Trình biên dịch Virtual Machine Emulator
Bước 4: Tiến hành biên dịch thư mục và thực hiện tập lệnh kiểm tra trên VM Emulator đã được cung cấp, đồng thời đảm bảo rằng quá trình so sánh với tệp so sánh hoàn tất thành công.
Bảng 2.9: So sánh quá trình biên dịch của các thư mục
OSClass.jack Chương trình thử nghiệm Kết quả kiểm tra mong muốn
Main.jack ArrayTest.tst ArrayTest.cmp
Main.jackMathTest.tst MathTest.cmp
Main.jack MemoryTest.tst MemoryTest.cmp
TRIỂN KHAI HỆ ĐIỀU HÀNH
Sau khi kiểm tra thành công từng lớp hệ điều hành riêng biệt, hãy tiến hành kiểm tra toàn bộ việc triển khai hệ điều hành bằng trò chơi Pong Bạn cần đặt tất cả các tệp jack vào thư mục dự án / 11 / Pong, sau đó biên dịch và thực thi trò chơi trong trình giả lập VM được cung cấp.
Hình 3.19: Gift mô phỏng trò chơi Pong
TỔNG KẾT
Triển khai một máy ảo và trình biên dịch cho ngôn ngữ lập trình đơn giản, đồng thời phát triển hệ điều hành cơ bản, giúp thu hẹp khoảng cách giữa ngôn ngữ cấp cao và phần cứng Dự án thực tế này không chỉ áp dụng kiến thức đã học mà còn nâng cao kỹ năng lập trình và khoa học máy tính ứng dụng, bao gồm xử lý ngăn xếp, phân tích cú pháp, tạo mã, cũng như các thuật toán cổ điển và cấu trúc dữ liệu để quản lý bộ nhớ, đồ họa vector và xử lý đầu vào-đầu ra, tất cả đều là những yếu tố cốt lõi của hệ thống máy tính hiện đại.
Bảng 4.10: Bảng phân công công việc
Họ tên MSSV Nhiệm vụ Đánh giá Đinh Thị
Tìm hiểu và giới thiệu JackOS.
Lên ý tưởng và hướng dãn thực hiện.
Tìm hiểu công dụng và xây dựng Array, Output.
Tìm hiểu công dụng và xây dựng Math, String.
Tìm hiểu công dụng và xây dựng Memory, Sys.
Tìm hiểu công dụng và xây dựng Keyboard, Screen.