Chuyển đến nội dung chính (Skip to content)
Mục lục bài viết
Open Source Essentials

Open Source Essentials 051.1: Những nguyên tắc cơ bản về Phần mềm

Open Source Essentials 051.1: Những nguyên tắc cơ bản về Phần mềm

Phân biệt source, executable, bytecode, compiler, linker và thư viện; hiểu dependency, runtime và phiên bản qua lab C sửa source, build lại và khôi phục.

Một công cụ được bàn giao với source code và một tệp chạy sẵn. Nhân viên sửa thông báo trong source nhưng chương trình vẫn in nội dung cũ. Người khác thử chạy bản build dành cho kiến trúc khác rồi nhận lỗi định dạng. Hai sự cố đều dễ hiểu hơn khi phân biệt mã nguồn, artifact sau build và môi trường thực thi.

Bài 051.1, Những nguyên tắc cơ bản về Phần mềm, tham chiếu Open Source Essentials 050-100 trang in 2–20. Nội dung được viết độc lập, đối chiếu ngày 23/10/2026. Bạn cần thao tác được tệp và terminal; lab cung cấp mã ngắn, không yêu cầu học C đầy đủ. Sau bài học, bạn nên vẽ được đường từ source tới chương trình chạy, nhận biết thư viện và giải thích vì sao một thay đổi chưa chắc có mặt trong artifact đang dùng.

Phần mềm, thuật toán và biểu diễn

Phần mềm gồm chương trình cùng các thành phần liên quan giúp máy thực hiện công việc. Source là dạng mã mà người phát triển dùng để sửa; executable là dạng có thể được hệ thống hoặc runtime thực thi theo quy tắc của môi trường. Một gói phân phối còn có thể chứa tài nguyên, cấu hình, tài liệu và thư viện. Không gọi mọi tệp trong gói là mã máy.

Thuật toán là cách giải bài toán theo các bước có ý nghĩa, chẳng hạn đếm yêu cầu đang mở. Cùng một ý tưởng có thể được thể hiện bằng nhiều ngôn ngữ. Cú pháp hợp lệ chưa bảo đảm thuật toán đúng: một hàm đếm mọi trạng thái khác closed sẽ cho kết quả sai nếu còn archived. Vì vậy cần dữ liệu thử đại diện, trường hợp biên và tiêu chí đầu ra.

Phần cứng thực thi chỉ dẫn theo kiến trúc của nó. File nhị phân cho một kiến trúc/ABI không mặc nhiên chạy được trên kiến trúc khác. Một container cũng không biến mọi instruction set thành tương thích; cơ chế giả lập nếu có là một lớp khác cần nhận diện. Khi tải phần mềm, kiểm tra hệ điều hành, kiến trúc, runtime và dependency cùng phiên bản.

Biên dịch và thông dịch là cơ chế, không phải nhãn tuyệt đối

Compiler chuyển một dạng chương trình sang dạng khác, có thể là object code, mã máy hoặc biểu diễn trung gian. Linker kết hợp object và thư viện theo mô hình liên kết để tạo kết quả. Assembler xử lý hợp ngữ thành dạng phù hợp; hợp ngữ có tên lệnh dễ đọc hơn các bit thô, nhưng macro và pseudo-instruction khiến quan hệ một dòng một instruction không phải quy tắc tuyệt đối.

Interpreter thực thi biểu diễn chương trình theo cách của nó. Các implementation hiện hành có thể kết hợp bytecode, interpreter và JIT. Ví dụ CPython thường biên dịch source thành bytecode cho máy ảo của nó; engine JavaScript có thể dùng nhiều tầng thực thi. Không nên dạy rằng mọi ngôn ngữ thông dịch luôn đọc từng dòng source rồi chạy ngay, hoặc mọi mã máy chắc chắn nhanh nhất trong mọi phép đo.

Bytecode là biểu diễn trung gian cho runtime tương ứng, không phải chỉ dẫn máy chạy trực tiếp trên mọi CPU. Khả năng mang sang môi trường khác còn phụ thuộc phiên bản runtime, API hệ thống, thư viện và phần native đi kèm. JavaScript được gửi tới browser thường là source text, nên lấy chính tệp JS tải xuống làm ví dụ “file bytecode dùng chung” sẽ gây nhầm.

Máy ảo runtime trong câu chuyện bytecode khác máy ảo hệ điều hành dùng để cài Ubuntu trong bài Linux. Cả hai đều là lớp trừu tượng nhưng cung cấp giao diện khác nhau. Một runtime VM thực thi chương trình theo mô hình của nó; VM hệ thống mô phỏng hoặc ảo hóa môi trường phần cứng cho guest OS.

Thư viện và linking

Thư viện tập hợp chức năng có thể tái sử dụng; nó không bắt buộc nằm trong một tệp duy nhất. Package có thể chứa nhiều module, mã native và tài nguyên. Package manager giúp lấy và quản lý các gói, nhưng tên package không tự chứng minh độ tin cậy hoặc quyền phân phối mọi thành phần.

Liên kết tĩnh đưa mã cần thiết từ thư viện vào artifact theo quy trình build. Liên kết động để chương trình dùng shared library phù hợp lúc nạp hoặc chạy. “Dùng thư viện tĩnh” không luôn có nghĩa toàn bộ executable không còn dependency động: nó có thể vẫn dùng libc động. Lab dưới minh họa thư viện tự viết được lấy từ archive vào executable, không tuyên bố build hoàn toàn static.

Hai cách có tradeoff về đóng gói, cập nhật, tương thích ABI và tài nguyên. Shared library có thể được nhiều chương trình dùng, nhưng không bảo đảm luôn ít RAM hơn hoặc nhanh hơn trong mọi tải. Với static dependency, cập nhật thư viện thường đòi rebuild/redeploy các artifact chứa mã đó. Với dynamic dependency, thay thư viện cũng cần kiểm tra tương thích, không chỉ chép một tệp mới lên hệ thống.

Source đi qua compile, archive và link để tạo executable của môi trường cụ thể.
Hình 1. Source đi qua compile, archive và link để tạo executable của môi trường cụ thể. Mở hình để xem chi tiết.

Dependency, phiên bản và nguồn gốc

Khi nhận một gói, ghi source ref, phiên bản compiler/runtime, dependency và artifact đang chạy. Hash giúp xác nhận byte có khớp bản tham chiếu, nhưng hash lấy từ cùng một nguồn chưa tin cậy không tự xác thực nhà phát hành. Chữ ký hoặc provenance cũng cần quy tắc kiểm tra danh tính và mối liên hệ tới artifact cụ thể.

SemVer mô tả ý nghĩa major/minor/patch dựa trên public API và chính sách của dự án, không dựa vào số tháng giữa các bản. Không phải mọi dự án có ba số đều tuân SemVer. Đọc release notes và hỗ trợ còn hiệu lực trước khi cập nhật. Phiên bản lớn hơn cũng không tự chứng minh ít lỗi hơn hoặc tương thích với ứng dụng của bạn.

Source công khai cho phép quan sát kỹ thuật, nhưng quyền sử dụng, sửa và phân phối còn cần giấy phép. Các bài 052 sẽ đọc điều kiện đó. Tại đây chỉ ghi rõ thành phần nào được dùng và từ đâu, tránh kết luận pháp lý từ kiểu linking hoặc sự tồn tại của source mà chưa có văn bản và ngữ cảnh.

Quản lý bộ nhớ và mô hình lập trình ở mức nhận biết

Ngôn ngữ/runtime có thể cung cấp quản lý bộ nhớ thủ công, garbage collection hoặc các cơ chế khác. GC thu hồi một số đối tượng không còn reachable theo mô hình của runtime, nhưng không ngăn mọi rò rỉ logic: ứng dụng vẫn có thể giữ tham chiếu tới dữ liệu không còn cần. Không dùng “có GC” làm lý do bỏ kiểm tra mức dùng bộ nhớ.

Concurrency là việc nhiều công việc cùng tiến triển trong một khoảng thời gian; parallelism là thực thi đồng thời về vật lý theo tài nguyên phù hợp. Nhiều thread/process cũng kéo theo phối hợp và trao đổi dữ liệu. Chia ứng dụng thành nhiều tiến trình không mặc nhiên làm nó nhanh hơn. Bài kiến trúc tiếp theo sẽ xét ranh giới thành phần thay vì triển khai hệ thống phân tán nâng cao.

OOP, thủ tục, hàm và khai báo là các cách tổ chức hoặc mô tả chương trình. Một ngôn ngữ có thể hỗ trợ nhiều cách; chọn class không tự bảo đảm dễ bảo trì. Với lab nhỏ, hàm tách chức năng đủ để quan sát. AI có thể hỗ trợ tạo source nhưng không thay việc hiểu đầu vào, kiểm tra thuật toán và review trước khi nhận artifact.

Lab: source đổi, executable cũ chưa đổi

Dùng môi trường Linux lab có compiler C, archiver ar và shell. Tạo thư mục riêng. Các lệnh chỉ tạo source cùng artifact DEMO trong thư mục này, không cài package vào hệ thống và không mở mạng. Bản kiểm chứng dùng GCC của Alpine, nên đường dẫn thư viện hệ thống có thể khác Ubuntu; ý nghĩa các bước vẫn được giải thích riêng.

Lưu metric.c, một thư viện giả lập nhỏ:

int demo_total(int first, int second) {
  return first + second;
}

Lưu main.c. Khai báo hàm cho compiler biết giao diện; implementation sẽ được linker lấy từ thư viện ở bước sau. Hàm main chỉ gọi dữ liệu số cố định và in ra stdout.

#include <stdio.h>
int demo_total(int first, int second);
int main(void) {
  printf("DEMO total: %d\n", demo_total(2, 3));
  return 0;
}

Biên dịch và liên kết:

cc -Wall -Wextra -Werror -c metric.c -o metric.o
ar rcs libmetric.a metric.o
cc -Wall -Wextra -Werror main.c ./libmetric.a -o demo
./demo

Kỳ vọng “DEMO total: 5”. Bước -c tạo object mà chưa link thành chương trình hoàn chỉnh; ar tạo archive; bước cc cuối kết hợp main với library archive. Tên .a gợi loại artifact ở môi trường này, không phải cơ chế bảo mật. Một file có đuôi đúng vẫn cần được kiểm tra nguồn và định dạng.

Sửa riêng metric.c thành return first + second + 1;, lưu lại rồi chạy ./demo mà chưa build. Nó vẫn in 5: executable đã có mã từ lần link trước. Chỉ sau khi chạy lại cả ba lệnh build, kết quả mới thành 6. Đây là bằng chứng một thay đổi source chưa được phát hành, không phải lý do xóa cache browser hoặc khởi động lại máy.

Khôi phục metric.c về phép cộng ban đầu, build lại và xác nhận 5. Nếu đổi giao diện hàm, cần kiểm tra mọi nơi dùng giao diện đó; lab này chỉ thay implementation nhỏ. Dùng cảnh báo compiler và exit status để nhận ra build thất bại, không tiếp tục chạy artifact cũ rồi tưởng build mới đã thành công.

Chỉ sửa source giữ kết quả cũ; rebuild cập nhật artifact, rồi khôi phục xác nhận kết quả.
Hình 2. Chỉ sửa source giữ kết quả cũ; rebuild cập nhật artifact, rồi khôi phục xác nhận kết quả. Mở hình để xem chi tiết.

Lab được chạy ngày 23/10/2026 trong container Alpine ARM64 với GCC 15.2.0 và GNU ar 2.45.1, user không phải root, không mạng ngoài. Thư mục tạm cho phép thực thi artifact tự biên dịch; filesystem khác chỉ đọc. Assertion xác nhận chuỗi kết quả 5/5/6/5 và các bước build thành công. Phép thử Python riêng xác nhận int của float 5.73 là 5, còn chuỗi “5.73” gây ValueError. Chưa đo hiệu năng, RAM, dynamic linking, bytecode hoặc chéo kiến trúc.

Cách ghi kết quả và giới hạn

Hồ sơ lab nên có source trước/sau, phiên bản cc/ar, lệnh build, exit status và ba kết quả 5/5/6 cùng lần khôi phục 5. Không cần đo thời gian để chứng minh một sự kiện về build. Lab chưa so static/dynamic ở mức RAM, chưa tạo bytecode và chưa chạy chéo kiến trúc; các phần đó là kiến thức giải thích, không phải kết quả đã thử ở đây.

Trong tài liệu cũ có ví dụ int(input()) chuyển chuỗi thập phân thành số nguyên. Với Python, cần phân biệt int của số float với int của chuỗi như “5.73”: trường hợp chuỗi đó không được chấp nhận theo cú pháp số nguyên thông thường và gây ValueError. Luôn xem kiểu dữ liệu đầu vào trước khi suy ra hành vi chuyển đổi. Bài này không cần chạy Python để hoàn thành lab C.

Tự kiểm tra

Object file khác executable ở đâu? Liên kết một archive tự viết có làm executable hoàn toàn static không? Vì sao source đổi mà chương trình cũ không đổi? Bytecode có tự chạy trên mọi máy không? Thư viện động có luôn tốt hơn không? Trả lời bằng đường build, dependency và môi trường cụ thể giúp bạn đặt câu hỏi đúng khi nhận phần mềm mã nguồn mở.

Nguồn tham khảo

Bài liên quan trong bộ LPI

Tìm hiểu thêm