24/05/2018, 17:17

Hợp ngữ

(assembly language) là một ngôn ngữ cấp thấp dùng để viết các chương trình máy tính. Cách dùng các thuật nhớ (mnemonics) thân thiện để viết chương trình đã thay thế cách lập trình trực tiếp lên máy tính bằng mã máy dạng số (numeric machine code) - từng áp ...

(assembly language) là một ngôn ngữ cấp thấp dùng để viết các chương trình máy tính. Cách dùng các thuật nhớ (mnemonics) thân thiện để viết chương trình đã thay thế cách lập trình trực tiếp lên máy tính bằng mã máy dạng số (numeric machine code) - từng áp dụng cho những máy tính đầu tiên - vốn rất mệt nhọc, dễ gây lỗi và tốn nhiều thời giờ. Một chương trình viết bằng hợp ngữ sẽ được dịch sang ngôn ngữ máy bằng một tiện ích gọi là trình hợp dịch. Lưu ý rằng, trình hợp dịch khác hoàn toàn với trình biên dịch, vốn dùng để biên dịch các ngôn ngữ cấp cao sang các chỉ thị lệnh cấp thấp mà sau đó sẽ được trình hợp dịch chuyển đổi sang ngôn ngữ máy. Các chương trình hợp ngữ thường phụ thuộc chặt chẽ vào một kiến trúc máy tính xác định, nó khác với ngôn ngữ cấp cao thường độc lập đối với các nền tảng kiến trúc phần cứng. Nhiều trình hợp dịch phức tạp ngoài các tính năng cơ bản còn cung cấp thêm các cơ chế giúp cho việc viết chương trình, kiểm soát quá trình dịch cũng như việc gỡ rối được dễ dàng hơn. đã từng được dùng rộng rãi trong tất cả các khía cạnh lập trình, nhưng ngày nay nó có xu hướng chỉ được dùng trong một số lãnh vực hẹp, chủ yếu để giao tiếp trực tiếp với phần cứng hoặc xử lý các vấn đề liên quan đến tốc độ cao điển hình như các trình điều khiển thiết bị, các hệ thống nhúng cấp thấp và các ứng dụng thời gian thực.

Trình hợp dịch (Assembler)

Thông thường, một trình hợp dịch hiện đại tạo ra mã nhị phân (object code) bằng cách phiên dịch các lệnh hợp ngữ thành mã thực thi (opcodes) và phân tích các biểu danh (symbolic names) ứng với các vùng nhớ cùng các thực thể khác. Việc dùng các biểu danh để tham chiếu là một tính năng then chốt của các trình hợp dịch, nó tiết kiệm một khối lượng lớn công việc tính toán và sửa đổi thủ công sau mỗi lần cải tiến ứng dụng. Hầu hết các trình hợp dịch đều hỗ trợ macro nhằm giúp cho việc thay thế một nhóm lệnh bằng một định danh ngắn gọn. Trong quá trình dịch, nhóm lệnh tương ứng sẽ được chèn trực tiếp vào vị trí macro thay vì một lời gọi hàm (subroutine). Các trình hợp dịch nói chung dễ tạo hơn so với các chương trình dịch cho ngôn ngữ cấp cao. Những trình hợp ngữ đầu tiên xuất hiện từ những thập niên 1950, trong buổi đầu sơ khai của máy tính đã tạo ra một bước ngoặt lớn đối với những lập trình viên vốn rất mệt mỏi vì việc lập trình bằng ngôn ngữ máy. Các trình hợp dịch hiện đại ngày nay, đặc biệt cho các dòng chip RISC như MIPS, Sun SPARC và HP PA-RISC, thường tối ưu việc sắp xếp và đồng bộ các chỉ thị lệnh (instruction scheduling) để tận dụng các kênh chuyền dữ liệu (pipeline) của CPU một cách hiệu quả.

Nhiều trình hợp dịch cấp cao còn hỗ trợ khả năng ngôn ngữ trừu tượng như:

  • Các cấu trúc điều khiển nâng cao
  • Các khai báo hàm cấp cao
  • Các kiểu dữ liệu trừu tượng cấp cao bao gồm các structures/records, unions, classes, và sets
  • Các macro phức tạp

Tham khảo phần Thiết kế ngôn ngữ bên dưới để rõ hơn.

(Assembly language)

Một chương trình viết bằng hợp ngữ bao gồm một chuỗi các lệnh (instructions) dễ nhớ tương ứng với một luồng các chỉ thị khả thi (executable) mà khi được dịch bằng một trình hợp dịch, chúng có khả năng nạp được vào bộ nhớ đồng thời thực thi được. Ví dụ, bộ vi xử lý x86/IA-32 có thể thực hiện được chỉ thị nhị phân sau (thể hiện ở dạng ngôn ngữ máy):

  • 10110000 01100001 (thập lục phân: 0xb061)

Lện trên tương đương với một chỉ thị hợp ngữ dễ nhớ hơn sau:

  • mov al, 061h

Chỉ thị lệnh trên có nghĩa là: gán giá trị thập lục phân 61 (97 dạng thập phân) cho thanh ghi trong bộ vi xử lý có tên là "al". Thuật từ “mov” là mã thực thi (operation code / opcode), được người thiết kế tập lệnh đặt tên thay thế cho từ “move”, các đối/ tham số của lệnh theo sau và ngăn cách với opcode bởi một dấu phảy “,”.

Trình hợp dịch thực hiện chuyển đổi hợp ngữ sang ngôn ngữ máy và trình phân dịch (disassembler) thực hiện quá trình trên ngược lại. Không giống các ngôn ngữ bậc cao, các chỉ thị hợp ngữ cơ bản thường có mối liên hệ tương ứng 1-1 với các chỉ thị ngôn ngữ máy. Tuy nhiên trong một số trường hợp, một trình hợp dịch có thể bổ sung các lệnh giả (pseudo-instructions) vào tập lệnh ngôn ngữ máy nhằm cung cấp các chức năng được dùng thường xuyên. Hầu hết các trình hợp dịch đa chức năng đều cung cấp thêm một tập macro phong phú để nhà sản xuất thiết bị và lập trình viên có thể tạo các mã lệnh và các dãy dữ liệu phức tạp.

Mỗi kiến trúc máy tính đều có ngôn ngữ máy riêng và do đó cũng có hợp ngữ riêng, chúng phân biệt với nhau bằng số lượng và kiểu của các lệnh mà chúng hỗ trợ. Chúng cũng có thể khác nhau về số lượng và kích cỡ của các thanh ghi cũng như cách thể hiện các kiểu dữ liệu trong bộ lưu trữ (bộ nhớ). Hầu hết các máy tính công dụng chung đều có khả năng thực hiện cùng chức năng nhưng cách mà chúng thực hiện thì khác nhau, điều đó phản ánh sự khác nhau giữa các hợp ngữ tương ứng với mỗi kiểu máy tính.

Ngôn ngữ máy (Machine language)

Ngôn ngữ máy được xây dựng từ các chỉ thị và các lệnh rời rạc, tùy vào mỗi kiến trúc xử lý mà tập lệnh được xác lập bởi các đặc thù riêng:

  • Các thanh ghi dùng cho tính toán số học
  • Cách bố trí bộ nhớ và tính địa chỉ
  • Cách điều khiển rẽ nhánh
  • Các kiểu đánh địa chỉ đặc thù dùng để giải các toán hạng

Nhiều lệnh phức hợp được tạo dựng bằng cách kết hợp nhiều chỉ thị đơn giản với nhau, các chỉ thị này tuân theo nguyên lý máy tính Von Neumann, tức là thực thi tuần tự và rẽ nhánh theo lệnh phân luồng. Một số lệnh điển hình có mặt trong hầu hết các tập lệnh gồm có:

  • Lệnh gán
    • Gán cho một thanh ghi (một vùng nhớ tạm thời trong CPU) một giá trị hằng số xác định
    • Chuyển dữ liệu từ một vùng nhớ sang một thanh ghi hoặc ngược lại. Thao tác này dùng để chuẩn bị dữ liệu cho một tính toán sau đó hoặc để lưu kết quả của một tính toán trước đó.
    • Đọc /ghi dữ liệu từ/vào các thiết bị phần cứng
  • Lệnh cho tính toán
    • Cộng, trừ, nhân hoặc chia các giá trị chứa trong các thanh ghi và lưu kết quả vào một thanh ghi
    • Thực hiện các phép thao tác bit “và”/”hoặc” (AND/OR) trên một cặp thanh ghi, hoặc phép phủ định bit trên một thanh ghi
    • So sánh nhỏ hơn/lớn hơn/ bằng nhau giữa hai giá trị lưu trong hai thanh ghi
  • Lệnh điều khiển rẽ nhánh
    • Nhảy tới một vị trí trong chương trình và thực thi các lệnh ở đó
    • Nhảy tới một vị trí khác nếu một điều kiện nhất định được thỏa mãn
    • Nhảy tới một vị trí nhưng lưu lại vị trí của lệnh tiếp theo để làm điểm nhảy trở về (thường là một lời gọi hàm)

Một số máy tính bao gồm các chỉ thị lệnh phức hợp trong tập lệnh của chúng. Một lệnh phức hợp thường thực hiện những tác vụ cần nhiều chỉ thị lệnh trên nhiều máy khác nhau, chúng thực hiện trong nhiều bước, điều khiển nhiều đơn vị chức năng. Danh sách minh họa một số lệnh phức hợp:

  • Lưu lại nhiều thanh ghi trên ngăn xếp chỉ một lần
  • Di chuyển các khối vùng nhớ lớn
  • Các phép toán dấu phảy động phức tạp (sine, cosine, square root, etc.)
  • Các lệnh ALU liên kết với một toán hạng từ bộ nhớ thay vì với một thanh ghi

Một kiểu lệnh phức hợp được dùng phổ biến ngày nay là các phép toán SIMD hay các lệnh vector (vector instruction) có khả năng thực hiện cùng một phép toán số học trên nhiều phần của dữ liệu trong cùng một thời điểm. Các lệnh SIMD (single instruction multile data) cho phép thực hiện song song nhiều thuật toán liên quan đến xử lý âm thanh, hình ảnh và video một cách dễ dàng. Nhiều tập lệnh thực thi SIMD tích hợp trong CPU đã được thương mại hóa dưới các thương hiệu như MMX và SSE, SSE2, SSE3, SSE4 (Intel), 3DNow! (AMD), AltiVec (IBM), tm3260 và tm5250 (Nexperia - Philips) …

Chỉ thị lệnh trong hợp ngữ nói chung là đơn giản, không giống như trong ngôn ngữ bậc cao. Mỗi chỉ thị lệnh điển hình thường bao gồm một mã lệnh (operation/ opcode hay đơn giản là instruction) theo với một hoặc nhiều toán hạng (operands), hoặc không có toán hạng nào. Hầu hết các chỉ thị lệnh đều tham khảo tới một giá trị đơn hoặc cặp giá trị. Mỗi chỉ thị lệnh thường được mã hóa tương ứng trực tiếp với một chỉ thị ngôn ngữ máy khả thi đơn lẻ. Những thành phần thông thường có trong hầu hết các hợp ngữ gồm có:

  • Các định nghĩa dữ liệu (Data definitions). Các chỉ thị phụ cho phép lập trình viên dành riêng một vùng nhớ cho các câu lệnh ngôn ngữ máy tham khảo tới. Vùng nhớ này thường được khởi tạo bằng các ký tự, chuỗi và các kiểu dữ liệu cơ sở khác
  • Nhãn (Labels). Các định nghĩa dữ liệu được tham chiếu tới bằng cách sử dụng các định danh (nhãn hoặc ký hiệu) do lập trình viên chỉ định. Chúng có thể là các hằng số, các biến hay các thành phần của cấu trúc. Nhãn cũng có thể được gán cho các vùng mã thực thi như địa chỉ bắt đầu của một thủ tục hoặc đích nhảy tới của lệng GOTO. Hầu hết các trình hợp dịch đều có khả năng quản lý các nhãn một cách linh hoạt, giúp cho lập trình viên thao tác trên nhiều không gian tên khác nhau, tự động tính độ lệch địa chỉ trong các cấu trúc dữ liệu và tham chiếu tới các nhãn chứa giá trị định trước hay kết quả của một tính toán.
  • Chú dẫn (Comments). Giống như nhiều ngôn ngữ máy tính khác, hợp ngữ cũng hỗ trợ việc thêm các chú dẫn vào trong mã nguồn, chúng sẽ được trình hợp dịch bỏ qua trong quá trình biên dịch
  • Tập lệnh bó (Macros). Hầu hết các trình hợp dịch đều nhúng ngôn ngữ macro vào tập lệnh chính. Lập trình viên dùng macro để tránh phải lặp lại những đoạn mã tương tự, trong quá trình tiền biên dịch tùy vào các đối số của macro mà các lệnh trong thân macro sẽ được sửa đổi và chèn vào vị trí gọi macro. Ngoài ra macro cũng được nhà sản xuất thiết bị dùng để đóng gói các phép tính đặc biệt ví dụ như:
    • Các bộ vi xử lý 8 bit thường dùng macro để tăng hoặc giảm một giá trị 16 bit lưu trong hai byte liên tục, đây là phép toán thường phải thực hiện trong trong ba hoặc bốn chỉ thị lệnh đơn lẻ. (bộ vi xử lý MOS Technology 6502)
    • Nhà sản xuất cũng hỗ trợ các macro để dùng cho các giao tiếp hệ thống như các thao tác vào/ra (I/O) hay các yêu cầu cấp thấp từ hệ điều hành. Trong các máy tính lớn của IBM, các thư viện macro khổng lồ cung cấp các phương thức truy xuất và các dịch vụ hệ thống khác

Những tính năng trên được mượn từ các thiết kế ngôn ngữ bậc cao nên đã đơn giản hóa những vấn đề trong lập trình và bảo trì mã nguồn cấp thấp. Mã nguồn hợp ngữ thô cũng có thể tạo ra bằng các trình biên dịch ngôn ngữ bậc cao (compiler) hoặc bằng các trình phân dịch mã máy (disassembler), nhưng chúng thường không có chú dẫn cũng như các định danh dễ hiểu nên rất khó đọc.

Ngoài các đặc tính cơ bản ở trên, tuy nhiên vài hợp ngữ cũng có những tính năng ngoại lệ như:

  • Nhiều trình hợp dịch bao gồm các ngôn ngữ macro rất phức tạp, cho phép phối hợp với các phần tử của ngôn ngữ bậc cao như các biến tượng trưng, các lệnh điều kiện, các thao tác chuỗi và các phép tính số học. Do đó một macro có thể thay thế một lượng lớn các lệnh hợp ngữ hoặc các định nghĩa dữ liệu dựa trên các tham số của nó. Nó có thể dùng để sinh ra các cấu trúc kiểu bản ghi hay các vòng lặp “mở” (unrolled) hoặc dựa trên các tham số phức tạp nó có thể giải quyết trọn vẹn một thuật giải.

Trong quá khứ

Về mặt lịch sử, đã từng có một số lượng lớn các chương trình đã được viết hoàn toàn bằng hợp ngữ. Trước khi xuất hiện ngôn ngữ C vào những năm 1970 và đầu thập niên 1980, các hệ điều hành hầu như được độc quyền viết bằng hợp ngữ. Nhiều ứng dụng thương mại cũng được viết bằng hợp ngữ, bao gồm một khối lượng lớn các phần mềm cho máy tính lớn của IMB được các tập đoàn lớn viết. Cuối cùng thì ngôn ngữ COBOL và FORTRAN đã thay thế hợp ngữ mặc dù còn nhiều tổ chức vẫn giữ lại các kiến trúc ứng dụng kiểu hợp ngữ trong suốt thập niên 1980. Hầu hết các máy vi tính (micro-computer) buổi đầu chủ yếu vận hành bằng hợp ngữ, bao gồm các hệ điều hành và các ứng dụng lớn. Lý do là bởi các hệ thống này bị hạn chế về tài nguyên, thiết bị, bộ nhớ và kiến trúc hiển thị cũng như các dịch vụ hệ thống dễ lỗi. Lý do quan trọng hơn, có lẽ là sự thiếu hụt các trình biên dịch bậc cao tiên tiến vốn thích hợp cho các hệ thống vi tính. Các ứng dụng lớn viết bằng hợp ngữ điển hình như hệ điều hành CP/M và MS-DOS, bảng tính spreadsheet và Lotus-123 trong các máy IBM-PC đời đầu, và nhiều các trò chơi phổ biến cho máy Commodore 64. Thậm chí tới những năm 1990, nhiều các trò chơi video giải trí vẫn được viết bằng hợp ngữ, bao gồm các trò chơi cho máy Mega Drive/Genesis và Super Nintendo Entertainment System.

Ngoài ra còn một dạng "ứng dụng" không được khuyến khích đó là virus máy tính. Trong các những năm '80 và đầu những năm '90 hầu hết virus máy tính được viết bằng hợp ngữ, lý do là sự giảm thiểu kích thước của virus cũng như khả năng can thiệp sâu vào hệ thống của hợp ngữ.

Hiện nay

Đã từng có nhiều tranh luận về tiện dụng và hiệu năng của hợp ngữ so với các ngôn ngữ bậc cao, tuy ngày nay người ta ít chú ý tới điều đó nữa. vẫn đóng vai trò quan trong trong một số nhu cầu cần thiết. Nói chung, các trình biên dịch hiện đại ngày nay đều có khả năng biên dịch các ngôn ngữ bậc cao thành mã mà có thể thực thi nhanh ít nhất bằng hợp ngữ. Độ phức tạp của các bộ vi xử lý hiện đại cho phép tối ưu mã một cách hiệu quả, hơn nữa, phần lớn thời gian hoạt động của CPU rơi vào trạng thái rỗi bởi nó phải đợi kết quả từ cá các tính toán “thắt cổ chai” như các thao tác I/O và truy xuất bộ nhớ. Vì thế tốc độ thực thi mã thô (raw code) trở thành vấn đề ít quan trọng đối với hầu hết lập trình viên, sự xuất hiện các ngôn ngữ thông dịch (interpreted language) ngày càng nhiều là một minh chứng cho điều này.

Ngày nay có một số ít tình huống mà các chuyên gia thực sự muốn dùng hợp ngữ cho công việc của họ là:

  • Khi các thiết bị hoạt động độc lập mà không cần tài nguyên hay các thư viện liên kết với ngôn ngữ bậc cao. Đây có lẽ là trường hợp phổ biến nhất
  • Khi cần giao tiếp trực tiếp với phần cứng như trình điều khiển thiết bị, hoặc khi muốn dùng các chỉ thị vi xử lý mà trình biên dịch không tận dụng được
  • Khi cần tối ưu khắt khe như các thuật toán có dùng vòng lặp tiêu tốn nhiều năng lực xử lý
  • Khi một hệ thống cần phải viết mã thủ công để tận dụng nguồn tài nguyên hạn hẹp. Ngày nay điều đó có vẻ không phổ biến nữa do giá cả CPU giảm đồng thời hiệu năng hoạt động CPU đã cải thiện đáng kể.
  • Khi các ngôn ngữ bậc cao không thể áp dụng được trên một CPU mới hoặc CPU chuyên dụng.

Ngày nay lập trình viên có thể chọn một ngôn ngữ cấp thấp như C để viết các ứng dụng cần hiệu năng cao, tuy điều đó không dễ dàng bởi một ứng dụng viết bằng C sẽ không hiểu quả hơn ứng dụng viết bằng hợp ngữ. Ngoài ra, hợp ngữ vẫn còn được giảng dạy trong hầu hết các chương trình Khoa học máy tính, các khái niệm nền tảng vẫn có ý nghĩa quan trọng. Chẳng hạn như số học nhị phân, cấp phát bộ nhớ, xử lý ngăn xếp, mã hóa tập ký tự, xử lý ngắt và thiết kế trình dịch vẫn được nghiên cứu một cách chi tiết và hệ thống bất kể phần cứng máy tính hoạt động như thế nào. Cách hoạt động của máy tính được xác định bởi tập lệnh cơ sở của nó, vì vậy để hiểu các khái niệm cơ sở đó cách tốt nhất là nghiên cứu hợp ngữ của nó. May thay, hầu như các máy tính hiện đại đều có các tập lệnh tương tự nhau, do đó chỉ nắm được một hợp ngữ cũng có đủ để hiểu được các khái niệm cơ bản ở các hợp ngữ trên hệ thống khác.

Các ứng dụng điển hình

mã cấp thấp thường được dùng cho BIOS lưu trong ROM của một hệ thống để khởi tạo và kiểm tra phần cứng hệ thống trước khi khởi tạo hệ điều hành. Khi khởi tạo phần cứng hoàn thành, quyền điều khiển hệ thống sẽ được chuyển qua cho các phần mã thự thi khác (thường được viết bằng ngôn ngữ bậc cao). Điều này cũng đúng cho hầu hết các trình khởi động (boot loader).

Nhiều trình biên dịch chuyển đổi các ngôn ngữ bậc cao thành hợp ngữ trước khi biên dịch thực sự, điều này cho phép kiểm tra mã phục vụ mục đích gỡ rối và tối ưu. Các ngôn ngữ cấp thấp như C thường cung cấp các cú pháp đặc biệt cho phép nhúng trực tiếp hợp ngữ vào mã nguồn. Các chương trình tận dụng tính năng này như Nhân Linux có thể tạo ra các tầng trừu tượng để sử dụng trên nhiều kiến trúc phần cứng khác nhau.

cũng có giá trị trong kỹ thuật dịch ngược (reverse engineering). Các chương trình lớn vốn chỉ được phân phối dưới dạng mã máy, chúng thường dễ dàng dịch ngược thành hợp ngữ để kiểm tra nhưng rất khó dịch ngược ra mã ngôn ngữ bậc cao.

0