Kĩ Thuật Lập Trình Hệ Thống Linux [Chương 2]

Chương 2  Các khái niệm cơ bản


2.1 Lõi của hệ điều hành: Kernel


Thuật ngữ hệ điều hành thường được sử dụng với hai nghĩa khác nhau:

  • Để biểu thị toàn bộ gói bao gồm phần mềm trung tâm quản lý tài nguyên của máy tính và tất cả các công cụ phần mềm tiêu chuẩn đi kèm, chẳng hạn như thông dịch dòng lệnh, giao diện đồ họa người dùng , tiện ích tệp và trình soạn thảo.
  • Thu hẹp hơn, nói đến phần mềm trung tâm quản lý và phân bổ tài nguyên máy tính (ví dụ: CPU, RAM và thiết bị).

Thuật ngữ Kernel thường được sử dụng như một từ đồng nghĩa cho ý nghĩa thứ hai, và nó là với ý nghĩa của thuật ngữ hệ điều hành mà chúng ta quan tâm trong cuốn sách này.

Mặc dù có thể chạy các chương trình trên một máy tính không có Kernel, sự hiện diện của kernel giúp đơn giản hoá việc viết và sử dụng các chương trình khác, và tăng sức mạnh và tính linh hoạt cho các lập trình viên. Kernel thực hiện điều này bằng cách cung cấp một lớp phần mềm để quản lý các tài nguyên hạn chế của một máy tính. 

Linux Kernel thực thi thông thường nằm tại pathname /boot/vmlinuz, hoặc một cái gì đó tương tự. Nguồn gốc của tên tệp này là do lịch sử. Vào Triển khai đầu của UNIX, kernel được gọi là Unix. Các triển khai UNIX sau này đã triển khai bộ nhớ ảo, đổi tên kernel thành vmunix. Trên Linux, tên tệp sao chép tên hệ thống, với z thay thế x cuối cùng thành biểu thị rằng hạt nhân là một tệp thực thi được nén lại.

Những công việc của kernel


Hạt nhân thực hiện các tác vụ sau:

  • Lập lịch cho process: Máy tính có một hoặc nhiều đơn vị xử lý trung tâm(CPU), thực thi các lệnh của chương trình. Giống như các hệ thống UNIX khác, Linux là một hệ điều hành đa nhiệm có ưu tiên, Multitasking có nghĩa là nhiều process (tức là, các chương trình đang chạy) có thể đồng thời nằm trong bộ nhớ và mỗi chương trình có thể nhận được việc sử dụng (các) CPU. Ưu tiên có nghĩa là các quy tắc quản lý quy trình nào nhận được sử dụng CPU và thời gian được xác định bởi trình lập lịch kernel (chứ không phải bởi chính các process).
  • Quản lý bộ nhớ: Trong khi các bộ nhớ máy tính rất lớn theo các tiêu chuẩn của một hoặc hai thập kỷ trước, kích thước của phần mềm cũng tương ứng được phát triển, do đó bộ nhớ vật lý (RAM) vẫn là một tài nguyên giới hạn mà kernel phải chia sẻ giữa các process theo cách công bằng và hiệu quả. Như hệ điều hành hiện đại nhất, Linux sử dụng quản lý bộ nhớ ảo (Phần 6.4), một kỹ thuật đưa ra hai ưu điểm chính:
  1. Các process được phân lập với nhau, sao cho process không thể đọc hoặc sửa đổi bộ nhớ của process khác hoặc của kernel.
  2. Chỉ một phần của một process cần được lưu giữ trong bộ nhớ, do đó làm giảm yêu cầu bộ nhớ của từng process và cho phép nhiều quy trình hơn được giữ trong RAM cùng một lúc. Điều này dẫn đến việc sử dụng CPU tốt hơn, vì nó tăng khả năng, tại bất kỳ thời điểm nào, có ít nhất một quá trình mà CPU (s) có thể thực thi.


  • Cung cấp hệ thống tệp: Hạt nhân cung cấp hệ thống tệp trên đĩa, cho phép tệp được tạo, truy xuất, cập nhật, xóa, v.v.
  • Tạo và kết thúc các tiến trình: Hạt nhân có thể nạp một chương trình mới vào bộ nhớ, cung cấp cho nó các tài nguyên (ví dụ: CPU, bộ nhớ và quyền truy cập vào các tệp) cần để chạy. Ví dụ như một chương trình đang chạy là một quá trình. Khi quá trình đã hoàn thành việc thực thi, hạt nhân đảm bảo rằng các tài nguyên mà nó sử dụng được giải phóng để tái sử dụng sau này bởi các chương trình sau này.
  • Truy cập vào thiết bị: Các thiết bị (chuột, màn hình, bàn phím, ổ đĩa và băng, vv) gắn liền với một máy tính cho phép truyền thông tin giữa máy tính và thế giới bên ngoài, cho phép đầu vào, đầu ra hoặc cả hai. Hạt nhân cung cấp các chương trình với giao diện chuẩn hóa và đơn giản hóa quyền truy cập vào các thiết bị, trong khi đồng thời phân quyền truy cập theo nhiều quy trình cho từng thiết bị. 
  • Mạng: Kernel truyền và nhận tin nhắn mạng (gói) đại diện cho các process của người dùng. Nhiệm vụ này bao gồm định tuyến các gói mạng đến hệ thống đích.
  • Cung cấp giao diện lập trình ứng dụng lời gọi hệ thống (API): Các quy trình có thể yêu cầu kernel thực hiện các tác vụ khác nhau bằng cách sử dụng các điểm vào kernel được gọi là lời gọi hệ thống. API gọi hệ thống Linux là chủ đề chính của cuốn sách này. Phần 3.1 nêu chi tiết các bước xảy ra khi quá trình thực hiện lời gọi hệ thống.

Ngoài các tính năng trên, hệ điều hành đa người dùng như Linux thường cung cấp cho người dùng sự trừu tượng của một máy tính cá nhân ảo; nghĩa là, mỗi người dùng có thể đăng nhập vào hệ thống và hoạt động độc lập phần lớn với người dùng khác. Ví dụ, mỗi người dùng có không gian lưu trữ đĩa riêng của họ (thư mục chính). Ngoài ra, người dùng có thể chạy các chương trình, mỗi chương trình có một phần của CPU và hoạt động trong không gian địa chỉ ảo riêng và các chương trình này có thể truy cập độc lập vào các thiết bị và chuyển thông tin qua mạng. Hạt nhân giải quyết xung đột tiềm tàng khi truy cập tài nguyên phần cứng, bởi vì người dùng và quy trình thường không biết những xung đột.

Kernel mode and user mode


Kiến trúc bộ vi xử lý hiện đại thường cho phép CPU hoạt động trong ít nhất hai các chế độ khác nhau: chế độ người dùng và chế độ kernel (đôi khi còn được gọi là chế độ người giám sát ). Hướng dẫn phần cứng cho phép chuyển từ chế độ này sang chế độ khác. Tương ứng, các khu vực bộ nhớ ảo có thể được đánh dấu là một phần của không gian người dùng hoặc không gian hạt nhân. Khi chạy ở chế độ người dùng, CPU chỉ có thể truy cập bộ nhớ được đánh dấu là đang ở trong không gian người dùng; cố gắng truy cập bộ nhớ trong không gian kernel sẽ tạo ra ngoại lệ phần cứng. Khi chạy trong chế độ kernel, CPU có thể truy cập cả hai không gian bộ nhớ kernel và người dùng. Một số hoạt động nhất định chỉ có thể được thực hiện trong khi bộ xử lý hoạt động chế độ kernel. Ví dụ bao gồm thực hiện lệnh dừng để dừng hệ thống, truy cập phần cứng quản lý bộ nhớ và khởi tạo các hoạt động I / O của thiết bị. Bằng cách tận dụng lợi thế của thiết kế phần cứng này để đặt hệ điều hành vào không gian kernel, người triển khai hệ điều hành có thể đảm bảo rằng các quy trình của người dùng không thể truy cập vào các câu lệnh và cấu trúc dữ liệu của kernel, hoặc để thực hiện các hoạt động sẽ ảnh hưởng xấu đến hoạt động của hệ thống.

Process versus kernel views of the system


Trong nhiều công việc lập trình hàng ngày, chúng ta đã quen với việc suy nghĩ về lập trình theo cách định hướng theo process. Tuy nhiên, khi xem xét các chủ đề khác nhau được đề cập sau trong cuốn sách này, có thể hữu ích để định hướng lại quan điểm của chúng ta để xem xét mọi thứ từ góc nhìn của kernel. Để làm cho độ tương phản rõ ràng, chúng ta hiện đang xem xét cách mọi thứ hoạt động từ quan điểm process và quan điểm kernel.

Một hệ thống chạy thường có nhiều process. Đối với một process, nhiều thứ xảy ra không đồng bộ. Process thực hiện không biết khi nào sẽ đến lần tiếp theo, các process khác sẽ được lên lịch cho CPU (và theo thứ tự nào), hoặc khi kế hoạch tiếp theo được lên kế hoạch. Sự phân phối tín hiệu và sự xuất hiện của các sự kiện truyền thông interprocess, và có thể xảy ra tại bất cứ lúc nào cho một process. Nhiều thứ xảy ra một cách minh bạch cho một process. process không biết vị trí của nó nằm trong RAM hoặc nói chung, cho dù một phần cụ thể của không gian bộ nhớ của nó hiện đang cư trú trong bộ nhớ hoặc được tổ chức trong khu vực trao đổi (một khu vực dành riêng cho không gian đĩa được sử dụng để bổ sung RAM của máy tính). Tương tự, một quá trình không biết vị trí trên ổ đĩa mà các tệp mà nó truy cập đang được giữ; nó chỉ đơn giản là đề cập đến các tập tin theo tên. 

Một process hoạt động trong sự cô lập, nó không thể trực tiếp giao tiếp với một process khác. 
Process không thể tự tạo process mới hoặc thậm chí kết thúc sự tồn tại của chính nó. Cuối cùng, process không thể giao tiếp trực tiếp với các thiết bị đầu vào và đầu ra được gắn vào máy tính. Ngược lại, hệ điều hành có kernel biết và điều khiển mọi thứ. kernel tạo điều kiện cho việc chạy tất cả các tiến trình trên hệ thống. kernel quyết định quá trình tiếp theo sẽ có được quyền truy cập vào CPU, khi nào nó sẽ làm như vậy, và trong bao lâu. kernel duy trì cấu trúc dữ liệu chứa thông tin về tất cả các process đang chạy và cập nhật các cấu trúc này khi các process được tạo, thay đổi trạng thái và chấm dứt. Kernel duy trì tất cả các cấu trúc dữ liệu cấp thấp cho phép các tên tệp được chương trình sử dụng để được dịch sang các vị trí thực tế trên đĩa. Kernel cũng duy trì các cấu trúc dữ liệu ánh xạ bộ nhớ ảo của mỗi process vào bộ nhớ vật lý của máy tính và (các) vùng trao đổi trên đĩa. Tất cả các giao tiếp giữa các process được thực hiện thông qua các cơ chế được cung cấp bởi kernel. Để đáp lại các yêu cầu từ các process, kernel tạo ra các process mới và chấm dứt các process hiện có. Cuối cùng, kernel (đặc biệt là thiết bị trình điều khiển) thực hiện tất cả các giao tiếp trực tiếp với các thiết bị đầu vào và đầu ra, chuyển thông tin đến các process theo yêu cầu người dùng.

Sau đó trong cuốn sách này, chúng ta sẽ nói những thứ như “một process có thể tạo ra một process  khác”, “một process có thể tạo ra một đường ống”, “một process có thể ghi dữ liệu vào một tệp” và “một process có thể chấm dứt bằng cách gọi thoát ( Tuy nhiên, hãy nhớ rằng kernel trung gian làm tất cả những hành động như vậy, và những phát biểu này chỉ là viết tắt của "một process có thể yêu cầu kernel tạo ra một process khác, ”và cứ thế.

2.2 The Shell


Shell là một chương trình có mục đích đặc biệt được thiết kế để đọc các lệnh do người dùng nhập và thực thi các chương trình thích hợp để đáp ứng các lệnh đó. Một chương trình như vậy đôi khi được gọi là trình thông dịch. Cụm từ Shell đăng nhập được sử dụng để biểu thị quá trình được tạo để chạy Shell khi người dùng đăng nhập lần đầu. Trong khi trên một số hệ điều hành, trình thông dịch lệnh là một phần tích hợp của hạt nhân, trên hệ thống UNIX, Shell là một quá trình người dùng. Nhiều 
shell tồn tại và những người dùng khác nhau trên cùng một máy tính có thể sử dụng đồng thời các shell khác nhau. Một số Shell quan trọng có xuất hiện theo thời gian:

  • Bourne shell (sh): Đây là Shell lâu đời nhất được sử dụng rộng rãi, và được viết bởi Steve Bourne. Nó là Shell tiêu chuẩn cho phiên bản UNIX thứ bảy. The Bourne shell chứa nhiều tính năng quen thuộc trong tất cả các shell: chuyển hướng I / O, đường ống, tạo tên tập tin (globbing), biến, thao tác của biến môi trường, thay thế lệnh, thực thi lệnh nền và chức năng. Tất cả các triển khai UNIX sau này bao gồm shell Bourne và bất kỳ Shell nào khác mà chúng có thể cung cấp.
  • C shell (csh): Shell này được viết bởi Bill Joy tại Đại học California tại Berkeley. Tên xuất phát từ sự giống nhau của nhiều điều khiển luồng xây dựng Shell này cho các ngôn ngữ lập trình C. C Shell cung cấp một số tính năng tương tác hữu ích không có trong trình bao Bourne, bao gồm lịch sử lệnh, chỉnh sửa dòng lệnh, điều khiển công việc và bí danh. Shell C không tương thích ngược với Shell Bourne. Mặc dù Shell tương tác chuẩn trên BSD là Shell C, các kịch bản shell (được mô tả trong một thời điểm) thường được viết cho Shell Bourne, để có thể di chuyển qua tất cả các triển khai UNIX.
  • Korn shell (ksh): Shell này được viết như là người kế thừa Shell Bourne bởi David Korn tại Phòng thí nghiệm AT & T Bell. Trong khi duy trì tính tương thích ngược với Shell Bourne, nó cũng kết hợp các tính năng tương tác tương tự như những cái được cung cấp bởi trình Shell C.
  • Bourne shell (bash): Shell này là sự thực hiện lại của dự án GNU Shell Bourne. Nó cung cấp các tính năng tương tác tương tự như các tính năng có sẵn trong Shell C và Korn. Tác giả chính của bash là Brian Fox và Chet Ramey. Bash có lẽ là Shell được sử dụng rộng rãi nhất trên Linux. (Trên Linux, Bourne shell, sh, thực sự được cung cấp bởi bash mô phỏng sh càng chặt càng tốt.)

POSIX.2-1992 quy định một tiêu chuẩn cho Shell được dựa trên phiên bản hiện tại của Shell Korn. Ngày nay, Shell Korn và bash đều phù hợp với POSIX, nhưng cung cấp một số phần mở rộng cho tiêu chuẩn, và nhiều các phần mở rộng này khác nhau giữa hai trình Shell. Các Shell được thiết kế không chỉ để sử dụng tương tác, mà còn cho việc giải thích của các tập lệnh shell, là các tệp văn bản chứa các lệnh shell. Vì mục đích này, mỗi shell có các cơ sở thường được kết hợp với các ngôn ngữ lập trình: các biến, vòng lặp và câu lệnh điều kiện, các lệnh I / O và các hàm.

Mỗi shell thực hiện các nhiệm vụ tương tự, mặc dù với các biến thể trong cú pháp. Trừ khi đề cập đến hoạt động của một hệ Shell cụ thể, chúng ta thường tham khảo “shell”, với sự hiểu biết rằng tất cả các Shell hoạt động theo cách mô tả. Hầu hết các ví dụ trong cuốn sách này yêu cầu sử dụng bash shell, nhưng, trừ khi có ghi chú khác, người đọc có thể giả định các ví dụ này hoạt động theo cách tương tự trong các trình Shell loại Bourne khác

2.3 Users and Groups


Mỗi người dùng trên hệ thống được xác định duy nhất và người dùng có thể thuộc về các nhóm.

Users

Mỗi người dùng của hệ thống có một tên đăng nhập duy nhất (tên người dùng) và một tên tương ứng ID người dùng số (UID). Đối với mỗi người dùng, chúng được xác định bởi một dòng trong hệ thống tệp mật khẩu, / etc / passwd, bao gồm các thông tin bổ sung sau:

  • ID nhóm: ID nhóm số của nhóm đầu tiên trong số các nhóm mà người dùng là hội viên.
  • Thư mục chính: thư mục ban đầu mà người dùng được đặt vào sau khi đăng nhập.
  • Login shell: tên của chương trình được thực thi để diễn giải các lệnh của người dùng.

Bản ghi mật khẩu cũng có thể bao gồm mật khẩu của người dùng, dưới dạng được mã hóa.
Tuy nhiên, vì lý do bảo mật, mật khẩu thường được lưu trữ trong tệp mật khẩu riêng ẩn, chỉ có thể đọc được bởi người dùng đặc quyền.

Groups

Vì mục đích quản trị đặc biệt, để kiểm soát quyền truy cập vào các tệp và các tệp khác tài nguyên hệ thống  rất hữu ích khi tổ chức người dùng thành các nhóm. Ví dụ, những người trong một nhóm làm việc trên một dự án duy nhất và do đó chia sẻ một tập hợp các tệp phổ biến, tất cả có thể được làm thành viên của cùng một nhóm. Trong các triển khai UNIX ban đầu, người dùng có thể là thành viên của chỉ một nhóm. BSD cho phép người dùng đồng thời thuộc về nhiều nhóm, một ý tưởng được thực hiện bởi các triển khai UNIX khác và tiêu chuẩn POSIX.1-1990. Mỗi nhóm được xác định bởi một dòng duy nhất trong tệp nhóm hệ thống, /etc /group, bao gồm các thông tin sau:
  • Tên nhóm: tên (duy nhất) của nhóm.
  • ID nhóm (GID): ID số được liên kết với nhóm này.
  • Danh sách người dùng: danh sách tên đăng nhập được phân tách bằng dấu phẩy của người dùng là thành viên của nhóm này (và những người không được xác định khác là thành viên của nhóm bởi thuộc tính của trường ID nhóm của hồ sơ tệp mật khẩu của họ).


Superuser

Một người dùng, được gọi là superuser, có các đặc quyền đặc biệt trong hệ thống. Các
tài khoản superuser có ID người dùng 0 và thường có tên đăng nhập là "root". Trên các hệ thống UNIX điển hình, superuser bỏ qua tất cả các kiểm tra quyền trong hệ thống. Như vậy, siêu người dùng có thể truy cập bất kỳ tệp nào trong hệ thống, bất kể quyền trên tệp đó và có thể gửi tín hiệu đến bất kỳ process người dùng nào trong hệ thống. Các quản trị viên hệ thống sử dụng tài khoản superuser để thực hiện nhiều nhiệm vụ quản trị trên hệ thống

2.4 Single Directory Hierarchy, Directories, Links, and Files


Hạt nhân duy trì một cấu trúc thư mục phân cấp duy nhất để sắp xếp tất cả các tệp trong hệ thống. (Điều này trái ngược với các hệ điều hành như Microsoft Windows, trong đó mỗi thiết bị đĩa có phân cấp thư mục riêng của nó.) Tại cơ sở của phân cấp này là thư mục gốc, có tên / (dấu gạch chéo). Tất cả các tệp và thư mục đều là con cháu của thư mục gốc. Hình 2-1 cho thấy một ví dụ về cấu trúc tệp phân cấp này.



Trong hệ thống tệp, mỗi tệp được đánh dấu bằng một loại, cho biết loại tệp đó là gì. Một trong các loại tệp này biểu thị các tệp dữ liệu thông thường, thường được gọi là các tệp thông thường hoặc đơn giản để phân biệt chúng với các loại tệp khác. Các loại tệp khác bao gồm thiết bị, đường ống, socket, thư mục và liên kết tượng trưng (shortcut). thuật ngữ Tệp thường được sử dụng để biểu thị một tệp thuộc bất kỳ loại nào, không chỉ là một tệp thông thường.

Directories and links


Thư mục là một tệp đặc biệt có nội dung dưới dạng một bảng tên tệp được ghép đôi với tham chiếu đến các tệp tương ứng. Liên kết tên tệp-cộng-tham chiếu này được gọi là liên kết và tệp có thể có nhiều liên kết và do đó có nhiều tên, trong cùng hoặc trong các thư mục khác nhau. Thư mục có thể chứa liên kết đến cả tệp và các thư mục khác. Các liên kết giữa các thư mục thiết lập hệ thống phân cấp thư mục được hiển thị trong Hình 2-1.

Mỗi thư mục chứa ít nhất hai mục:. (dấu chấm), là một liên kết đến thư mục, và .. (dấu chấm), là một liên kết đến thư mục cha của nó, thư mục ở trên nó trong hệ thống phân cấp. Mỗi thư mục, ngoại trừ thư mục gốc, đều có một thư mục cha. Cho thư mục gốc, mục nhập dấu chấm là một liên kết đến thư mục gốc (do đó, / .. tương đương với /).

Symbolic links


Giống như một liên kết bình thường, một liên kết tượng trưng cung cấp một tên thay thế cho một tập tin. Nhưng trong khi một liên kết bình thường là một mục nhập tên tệp-plus-pointer trong một danh sách thư mục, một biểu tượng liên kết là một tệp được đánh dấu đặc biệt chứa tên của một tệp khác. (Nói cách khác, một liên kết tượng trưng có mục nhập tên tệp-plus-pointer trong một thư mục và tệp được gọi đến bởi con trỏ chứa một chuỗi có tên một tệp khác.) Tệp sau này thường được gọi là mục tiêu của liên kết tượng trưng, ​​và thường được gọi là liên kết tượng trưng “Points” hoặc “refers” đến tệp đích. Khi tên đường dẫn được chỉ định trong lời gọi hệ thống, trong hầu hết trường hợp, kernel tự động dereferences (hoặc đồng nghĩa, "follows") mỗi liên kết tượng trưng trong tên đường dẫn, thay thế nó bằng tên tệp mà nó chỉ. Quá trình này có thể xảy ra đệ quy nếu mục tiêu của một liên kết tượng trưng là chính nó là một liên kết tượng trưng. (Hạt nhân áp đặt các giới hạn về số lượng các tham chiếu để xử lý khả năng của các chuỗi vòng tròn của các liên kết tượng trưng.) Nếu một liên kết tượng trưng đề cập đến vào một tệp không tồn tại, nó được cho là một liên kết dangling.
Thường liên kết cứng và liên kết mềm được sử dụng như các thuật ngữ thay thế cho các liên kết bình thường và biểu tượng. Lý do có hai loại liên kết khác nhau được giải thích trong Chương 18.

File names


Trên hầu hết các hệ thống tệp Linux, tên tệp có thể dài tối đa 255 ký tự. Tên tệp có thể chứa bất kỳ ký tự nào ngoại trừ dấu gạch chéo (/) và ký tự rỗng (\ 0). Tuy nhiên khuyến khích chỉ sử dụng chữ cái và chữ số, và. (dấu chấm), _ (gạch dưới) và - (dấu gạch ngang) ký tự. Tập ký tự 65 ký tự này, [-._ a-zA-Z0-9], được đề cập trong SUSv3 như tập ký tự tên tệp portable.

Chúng ta nên tránh sử dụng các ký tự trong tên tệp không có trong bộ ký tự portable vì các ký tự đó có thể có ý nghĩa đặc biệt trong shell, trong các biểu thức chính quy hoặc trong các ngữ cảnh khác. Nếu tên tệp có chứa các ký tự có ý nghĩa đặc biệt xuất hiện trong các ngữ cảnh như vậy, thì các ký tự này phải được thoát; có nghĩa là, được đánh dấu đặc biệt thường với dấu gạch chéo ngược trước (\) -
để chỉ ra rằng chúng không nên được giải nghĩa với những ý nghĩa đặc biệt đó. Trong các ngữ cảnh không có cơ chế thoát, tên tệp không thể sử dụng được. Chúng ta cũng nên tránh tên tập tin bắt đầu bằng dấu nối (-), vì tên tệp như vậy có thể bị nhầm lẫn với các tùy chọn khi được chỉ định trong một lệnh trình shell.

Path names


Tên đường dẫn là một chuỗi bao gồm dấu gạch chéo ban đầu tùy chọn (/) được theo sau bởi một chuỗi tên tệp được phân tách bằng dấu gạch chéo. Tất cả trừ tên tệp thành phần cuối cùng xác định một thư mục (hoặc một liên kết tượng trưng để tham chiếu đến một thư mục). Thành phần cuối cùng của một tên đường dẫn có thể xác định bất kỳ loại tệp nào, bao gồm một thư mục. hàng loạt tên tệp thành phần trước dấu gạch chéo cuối cùng đôi khi được gọi là phần thư mục của tên đường dẫn, trong khi tên sau dấu gạch chéo cuối cùng đôi khi được gọi là phần hoặc phần cơ sở của tên đường dẫn. Một tên đường dẫn được đọc từ trái sang phải; mỗi tên tệp nằm trong thư mục được chỉ định bởi phần trước của tên đường dẫn. Chuỗi .. có thể được sử dụng ở bất cứ nơi nào trong một tên đường dẫn để chỉ vị trí cha mẹ của chúng được chỉ định trong path name.

Một tên đường dẫn mô tả vị trí của một tệp trong hệ thống phân cấp thư mục duy nhất, và là tuyệt đối hoặc tương đối:
  • Tên đường dẫn tuyệt đối bắt đầu bằng dấu gạch chéo (/) và chỉ định vị trí của tệp đối với thư mục gốc. Ví dụ về tên đường dẫn tuyệt đối cho tệp trong Hình 2-1 là /home/mtk/.bashrc, / usr / include và / (tên đường dẫn của thư mục gốc)danh mục).
  • Tên đường dẫn tương đối chỉ định vị trí của tệp tương ứng với luồng của quá trình thư mục làm việc (xem bên dưới), và được phân biệt với một tên đường dẫn tuyệt đối bởi sự vắng mặt của một dấu gạch chéo ban đầu. Trong Hình 2-1, từ thư mục usr, tệp types.h có thể được tham chiếu bằng cách sử dụng tên đường dẫn tương đối bao gồm / sys / types.h, trong khi từ thư mục avr, tệp .bashrc có thể được truy cập bằng cách sử dụng tên đường dẫn tương đối ../mtk/.bashrc.


Current working directory


Mỗi process có một thư mục làm việc hiện tại. Đây là "vị trí hiện tại" của process trong hệ thống phân cấp thư mục duy nhất, và nó là từ thư mục này mà các tên đường dẫn tương đối được diễn giải cho process. Một tiến trình kế thừa thư mục làm việc hiện tại của nó từ tiến trình cha của nó. shell đăng nhập có thư mục làm việc hiện tại ban đầu của nó được đặt thành vị trí có tên trong trường thư mục chính của mục nhập tệp mật khẩu của người dùng. Shell hiện tại đang hoạt động thư mục có thể được thay đổi bằng lệnh cd.

File ownership and permissions


Mỗi tệp có ID người dùng và ID nhóm xác định chủ sở hữu của tệp và nhóm mà nó thuộc về. Quyền sở hữu tệp được sử dụng để xác định quyền truy cập có sẵn cho người dùng của tệp. Với mục đích truy cập tệp, hệ thống chia người dùng thành ba loại: chủ sở hữu tệp (đôi khi được gọi là tệp người dùng), người dùng thành viên của nhóm khớp với ID nhóm của tệp và phần còn lại của nhóm khác. Ba bit quyền có thể được đặt cho từng danh mục người dùng này (thực hiện tổng cộng chín bit quyền): quyền cho phép đọc nội dung của tập tin, quyền cho phép ghi sửa đổi nội dung của tệp và quyền thực thi cho phép thực thi tệp, đó là chương trình hoặc tập lệnh được xử lý bởi một số trình thông dịch (thông thường, nhưng không phải luôn luôn, một trong các trình shell).

Các điều khoản này cũng có thể được đặt trên các thư mục, mặc dù ý nghĩa của chúng hơi khác: quyền cho phép đọc nội dung của (ví dụ: tên tệp trong) thư mục được liệt kê; quyền cho phép ghi nội dung của thư mục được đã thay đổi (tức là, tên tệp có thể được thêm, xóa và thay đổi); và quyền thực hiện (đôi khi được gọi là tìm kiếm) cho phép truy cập vào các tệp trong thư mục (tùy thuộc vào các quyền trên chính các tệp).

2. 5 File I/O Model


Một trong những đặc điểm phân biệt của mô hình I / O trên các hệ thống UNIX là khái niệm về tính phổ quát của I / O. Điều này có nghĩa là cùng một hệ thống gọi (open (), read (), write (), close (), vv) được sử dụng để thực hiện I / O trên tất cả các loại tệp, bao gồm thiết bị. (Kernel dịch các yêu cầu I / O của ứng dụng sang các hệ thống tệp hoặc trình điều khiển thiết bị thích hợp thực hiện I / O trên tệp đích hoặc thiết bị.)

Do đó, một chương trình sử dụng các lời gọi hệ thống này sẽ hoạt động trên bất kỳ loại tệp nào. Về cơ bản, kernel cung cấp một loại tệp: một chuỗi các byte tuần tự, trong trường hợp tệp đĩa, có thể được truy cập ngẫu nhiên sử dụng lệnh gọi hệ thống lseek ().

Nhiều ứng dụng và thư viện giải thích ký tự dòng mới (mã ASCII 10 thập phân, đôi khi còn được gọi là linefeed) như là chấm dứt một dòng văn bản và bắt đầu một dòng văn bản khác. Các hệ thống UNIX không có ký tự cuối tập tin; cuối tập tin được phát hiện bởi một lần đọc trả về không có dữ liệu.

File descriptors


Các lời gọi hệ thống I / O đề cập đến các tệp mở bằng cách sử dụng một bộ mô tả tệp, một số nguyên không âm (thường nhỏ). Trình mô tả tệp thường thu được bằng lệnh gọi open(), đối số là đường dẫn của tệp I / O tương ứng. Thông thường, một quá trình kế thừa ba bộ mô tả tệp được khởi chạy bằng shell: bộ mô tả 0 là đầu vào tiêu chuẩn, tệp mà từ đó quá trình lấy đầu vào; bộ mô tả 1 là đầu ra tiêu chuẩn, tệp mà quá trình ghi đầu ra của nó; và bộ mô tả 2 là lỗi tiêu chuẩn, tệp mà quá trình ghi tin nhắn lỗi và thông báo về các điều kiện đặc biệt hoặc bất thường. Trong một tương tác shell hoặc chương trình, ba mô tả này thường được kết nối với thiết bị đầu cuối. Trong thư viện stdio, các bộ mô tả này tương ứng với tệp stdin, stdout và stderr.

Thư viện stdio


Để thực hiện các tệp I / O, các chương trình C thường sử dụng các hàm I / O chứa trong thư viện C chuẩn. Tập hợp các hàm này, được gọi là thư viện stdio, bao gồm fopen (), fclose (), scanf (), printf (), fgets (), fputs (), v.v. Các chức năng stdio là lớp trên của các lời gọi hệ thống I / O (open (), close (), read (), write (), vv).

2.6 Chương trình

Các chương trình thường tồn tại dưới hai dạng. Dạng đầu tiên là mã nguồn, có thể đọc được bằng con người bao gồm một loạt các câu lệnh được viết bằng ngôn ngữ lập trình như như C. Để được thực hiện, mã nguồn phải được chuyển đổi sang dạng thứ hai: nhị phân, các hướng dẫn bằng ngôn ngữ máy mà máy tính có thể hiểu được. (Điều này trái ngược với một tập lệnh, là một tệp văn bản chứa các lệnh được xử lý trực tiếp bởi một chương trình như trình shell hoặc trình thông dịch lệnh khác.) Hai ý nghĩa của thuật ngữ chương trình thường được coi là đồng nghĩa, kể từ khi biên dịch và liên kết chuyển đổi mã nguồn thành mã máy nhị phân tương đương ngữ nghĩa.

Filter


Một bộ lọc là tên thường được áp dụng cho một chương trình đọc đầu vào của nó từ stdin, thực hiện một số phép biến đổi của đầu vào đó và ghi dữ liệu được chuyển đổi thành stdout. Ví dụ về các bộ lọc bao gồm cat, grep, tr, sort, wc, sed và awk

Command-line arguments

Trong C, các chương trình có thể truy cập các đối số dòng lệnh, các từ được cung cấp trên dòng lệnh khi chương trình được chạy. Để truy cập các đối số dòng lệnh, hàm main () của chương trình được khai báo như sau:

int int (int argc, char * argv [])

Biến argc chứa tổng số đối số dòng lệnh và các đối số riêng lẻ có sẵn dưới dạng các chuỗi được chỉ bởi các thành viên của mảng argv. Đầu tiên của chuỗi này, argv [0], xác định tên của chính chương trình

2.7 Processes

Để đơn giản nhất, một process là một thực thể của một chương trình thực thi. Khi một chương trình được thực thi, kernel tải mã của chương trình vào bộ nhớ ảo, phân bổ không gian cho các biến chương trình và thiết lập cấu trúc dữ liệu để ghi lại các thông tin khác nhau (chẳng hạn như ID process, trạng thái chấm dứt, ID người dùng và ID nhóm) về process.

Từ góc nhìn của kernel, các quá trình là các thực thể trong đó kernel phải chia sẻ các tài nguyên khác nhau của máy tính. Đối với tài nguyên bị giới hạn, chẳng hạn như bộ nhớ, kernel ban đầu phân bổ một số lượng tài nguyên cho xử lý và điều chỉnh phân bổ này trong suốt thời gian của quá trình để phản hồi nhu cầu của process và nhu cầu hệ thống tổng thể cho tài nguyên đó.
Khi process chấm dứt, tất cả các tài nguyên đó được phát hành để tái sử dụng bởi process khác. Các tài nguyên khác, chẳng hạn như CPU và băng thông mạng, có thể tái tạo, nhưng phải được chia sẻ công bằng giữa tất cả các quy trình

Process memory layout

Một process được phân chia hợp lý thành các phần sau, được gọi là phân đoạn:
  •  Text: câu lệnh của chương trình.
  •  Data: các biến tĩnh được chương trình sử dụng.
  •  Heap: một vùng mà từ đó các chương trình có thể tự động cấp phát bộ nhớ bổ sung.
  •  Stack: một phần bộ nhớ phát triển và co lại khi các hàm được gọi và return và được sử dụng để phân bổ lưu trữ cho các biến cục bộ và gọi hàm liên kết thông tin


 Process creation and program execution


Một Process có thể tạo một Process mới bằng cách sử dụng lệnh fork () hệ thống. Process gọi fork () được gọi là Process cha và Process mới được gọi là Process con. Kernel tạo ra Process con bằng cách tạo một bản sao của Process cha. Process con thừa kế các bản sao của dữ liệu của Process cha, ngăn xếp và phân đoạn heap, sau đó nó có thể sửa đổi độc lập với các bản sao của Process cha .(text chương trình, được đặt trong bộ nhớ được đánh dấu là read only, được chia sẻ bởi hai Process.)

Process con sẽ thực hiện một tập các hàm khác nhau trong cùng mã với cha hoặc thường xuyên sử dụng lệnh gọi hàm execve () để tải và thực hiện một chương trình hoàn toàn mới. Lệnh execve () hủy bỏ text, dữ liệu hiện có, ngăn xếp và phân đoạn heap, thay thế chúng bằng các phân đoạn mới dựa trên mã của chương trình mới.

Một số hàm thư viện C liên quan được xếp lớp trên của execve (), mỗi hàm cung cấp một giao diện hơi khác với cùng một chức năng. Tất cả các chức năng này có tên bắt đầu bằng toán tử chuỗi và sự khác biệt không quan trọng, chúng ta sẽ sử dụng ký hiệu exec () để tham chiếu chung cho các hàm này. Tuy nhiên, hãy lưu ý rằng không có hàm thực sự nào với tên exec (). Thông thường, chúng ta sẽ sử dụng động từ để thực hiện để mô tả hoạt động được thực hiện execve () và các hàm thư viện được phía trên nó.

Process ID and parent process ID


Mỗi process có một định danh số nguyên duy nhất (PID). Mỗi process cũng có thuộc tính định danh process cha (PPID), xác định process yêu cầu kernel để tạo ra process.

Process termination and termination status


Một Process có thể chấm dứt theo một trong hai cách: bằng cách tự yêu cầu chấm dứt sử dụng lời gọi hệ thống _exit () (hoặc chức năng thư viện exit () liên quan), hoặc bằng cách bị kill bằng cách phát các tín hiệu. Trong cả hai trường hợp, Process mang trạng thái chấm dứt, giá trị số nguyên không âm nhỏ có sẵn để kiểm tra bằng Process cha bằng cách sử dụng lời gọi hệ thống wait (). Trong trường hợp lời gọi đến _exit (), quá trình này sẽ chỉ định rõ ràng trạng thái chấm dứt của riêng nó. Nếu một quá trình bị kill bởi tín hiệu, trạng thái kết thúc được đặt theo loại tín hiệu gây ra sự chấm dứt của Process. (Đôi khi, chúng ta sẽ đề cập đến đối số được chuyển đến _exit () làm trạng thái thoát của quy trình, khác biệt với trạng thái chấm dứt, là giá trị được chuyển đến _exit () hoặc dấu hiệu của tín hiệu đã kill quá trình.)

Theo quy ước, trạng thái chấm dứt bằng 0 cho biết rằng quá trình đã thành công, và trạng thái khác chỉ ra rằng đã xảy ra lỗi. Hầu hết các shell gán cho trạng thái chấm dứt của chương trình thực thi cuối cùng thông qua một biến shell có tên là $?

Process user and group identifiers (credentials)

Mỗi process có một số ID người dùng được liên kết (UID) và ID nhóm (GID).
Bao gồm các:
  • ID người dùng thực và ID nhóm thực: Những người dùng này xác định người dùng và nhóm mà process thuộc về. Một process mới kế thừa các ID này từ cha của nó. Một shell đăng nhập nhận ID người dùng thực và ID nhóm thực của nó từ các trường tương ứng trong tệp mật khẩu hệ thống.
  • ID người dùng hiệu quả và ID nhóm hiệu quả: Hai ID này (kết hợp với ID nhóm bổ sung) được sử dụng để xác định các quyền mà process có khi truy cập các tài nguyên được bảo vệ như các tệp và các đối tượng truyền thông giữa các process. Thông thường, ID hiệu dụng của process  có cùng giá trị với ID thực tương ứng. Thay đổi ID hiệu dụng là một cơ chế cho phép một process giả định các đặc quyền của một người dùng khác hoặc nhóm 
  • ID nhóm bổ sung: Các ID này xác định các nhóm bổ sung mà process thuộc về. Một process mới kế thừa các ID nhóm bổ sung của nó từ cha của nó. Một shell đăng nhập nhận các ID nhóm bổ sung của nó từ tệp nhóm hệ thống.


Privileged processes

Theo truyền thống, trên các hệ thống UNIX, một process đặc quyền  là một process có ID người dùng hiệu dụng là 0 (superuser). process như vậy bỏ qua các giới hạn cho phép thường được áp dụng bởi kernel. Ngược lại, thuật ngữ "không có đặc quyền" được áp dụng cho các process do người dùng khác điều hành. Các process này phải tuân theo các quy tắc cho phép được thực thi bởi kernel. Một process có thể được đặc quyền bởi vì nó được tạo ra bởi một process đặc quyền khác — ví dụ, bởi một shell đăng nhập được bắt đầu bởi root (superuser). Một cách khác là một process có thể trở thành đặc quyền là thông qua cơ chế ID người dùng thiết lập, cho phép một quy trình giả định ID người dùng có hiệu lực giống với ID người dùng của tệp chương trình nó đang thực thi.

Capabilities


Kể từ kernel 2.2, Linux phân chia các đặc quyền theo truyền thống dành cho siêu người dùng vào một tập hợp các đơn vị riêng biệt được gọi là Capabilities. Mỗi hoạt động đặc quyền được liên kết với một Capabilities cụ thể và một process có thể thực hiện một thao tác chỉ khi nó hoạt động có Capabilities tương ứng. process siêu người dùng truyền thống (ID người dùng hiệu dụng là 0) tương ứng với một process với tất cả các khả năng được kích hoạt. Cấp một tập hợp các khả năng cho một quá trình cho phép nó thực hiện một số hoạt động bình thường được phép cho superuser, trong khi ngăn chặn nó hoạt động những người khác. Các khả năng được mô tả chi tiết trong Chương 39. Trong phần còn lại của cuốn sách, khi lưu ý rằng một hoạt động cụ thể chỉ có thể được thực hiện bởi một process đặc quyền, chúng ta thường sẽ xác định Capabilities cụ thể trong dấu ngoặc đơn. Tên Capabilities bắt đầu bằng tiền tố CAP_, như trong CAP_KILL.

The init process


Khi khởi động hệ thống, kernel tạo ra một process đặc biệt gọi là init, “parent” của tất cả các process, "có nguồn gốc từ tệp chương trình /sbin/init. Tất cả các quy trình trên hệ thống được tạo ra (sử dụng fork ()) hoặc bởi init hoặc bởi một trong các hậu duệ của nó. Quá trình init luôn có process ID 1 và chạy với các đặc quyền superuser. Các quá trình init không thể bị kill (ngay cả bởi superuser), và nó chỉ chấm dứt khi hệ thống bị tắt. Nhiệm vụ chính của init là tạo và theo dõi một loạt các các process được yêu cầu bởi một hệ thống đang chạy. (Để biết chi tiết, xem trang hướng dẫn sử dụng init (8).)

Daemon processes


Daemon là một process có mục đích đặc biệt được tạo và xử lý bởi hệ thống theo cách tương tự như các process khác, nhưng được phân biệt bởi những điều sau đặc điểm:
  • Nó tồn tại lâu. Một quá trình daemon thường bắt đầu khi khởi động hệ thống và vẫn còntồn tại cho đến khi hệ thống tắt.
  • Nó chạy ở chế độ nền và không có thiết bị đầu cuối điều khiển mà từ đó nó có thể đọc đầu vào hoặc để nó có thể ghi đầu ra.

Ví dụ về các daemon process bao gồm syslogd, ghi lại các thông báo trong nhật ký hệ thống và httpd, phục vụ các trang web thông qua Giao thức truyền siêu văn bản.

Environment list

Mỗi process có một danh sách môi trường, là một tập hợp các biến môi trường được duy trì trong bộ nhớ không gian người dùng của proces. Mỗi phần tử trong danh sách này bao gồm tên và giá trị được liên kết. Khi một proces mới được tạo qua fork (), nó thừa kế một bản sao của môi trường của cha nó. Do đó, môi trường cung cấp một cơ chế cho một proces cha để truyền đạt thông tin cho một tiến trình con. Khi một proces thay thế chương trình mà nó đang chạy bằng cách sử dụng exec (), thì proces mới hoặc kế thừa môi trường được chương trình cũ sử dụng hoặc nhận môi trường mới được chỉ định như một phần của lệnh exec ().

Các biến môi trường được tạo ra với lệnh export trong phần lớn các shell (hoặc lệnh setenv trong trình shell C), như trong ví dụ sau:

$ export MYVAR = 'hello world'

Các chương trình C có thể truy cập môi trường bằng biến external (char ** environ), và các hàm thư viện khác nhau cho phép một proces truy xuất và sửa đổi các giá trị trong môi trường. Các biến môi trường được sử dụng cho nhiều mục đích khác nhau. Ví dụ: shell định nghĩa và sử dụng một loạt các biến có thể được truy cập bởi các script và các chương trình được thực hiện từ trình shell. Chúng bao gồm biến HOME , xác định tên đường dẫn của thư mục đăng nhập của người dùng và biến PATH, chỉ định danh sách các thư mục mà trình shell sẽ tìm kiếm khi tìm kiếm các chương trình tương ứng với các lệnh do người dùng nhập.

Resource limits


Mỗi process tiêu thụ tài nguyên, chẳng hạn như các tệp mở, bộ nhớ và thời gian CPU. Sử dụng lời gọi hệ thống setrlimit (), một quá trình có thể thiết lập các giới hạn trên cho việc tiêu thụ nhiều tài nguyên khác nhau. Mỗi giới hạn tài nguyên như vậy có hai giá trị liên quan:giới hạn  phần mềm, giới hạn số lượng tài nguyên mà process có thể tiêu thụ; và giới hạn cứng, đó là một giá trị trần trên  mà giới hạn mềm có thể được điều chỉnh. proces không có đặc quyền có thể thay đổi giới hạn mềm của nó đối với một tài nguyên cụ thể thành bất kỳ giá trị nào trong phạm vi từ 0 đến giới hạn cứng tương ứng, nhưng chỉ có thể hạ thấp giới hạn cứng.

Khi một proces mới được tạo bằng fork (), nó sẽ kế thừa các bản sao của cài đặt giới hạn tài nguyên.
Giới hạn tài nguyên của shell có thể được điều chỉnh bằng lệnh ulimit (giới hạn trong shell C). Các cài đặt giới hạn này được kế thừa bởi các proces con shell tạo ra để thực thi lệnh.

Memory Mappings


Lời gọi hệ thống mmap () tạo ra một ánh xạ bộ nhớ mới trong quá trình gọi của Không gian địa chỉ ảo. Các ánh xạ được chia thành hai loại:
  • Ánh xạ tệp ánh xạ vùng của một tệp vào bộ nhớ ảo của quá trình gọi. Khi đã được ánh xạ, nội dung của tệp có thể được truy cập bằng các thao tác trên các byte trong vùng bộ nhớ tương ứng. Các trang của ánh xạ được tự động tải từ tệp theo yêu cầu.
  • Ngược lại, ánh xạ ẩn danh không có tệp tương ứng. Thay vào đó, các trang của ánh xạ được khởi tạo thành 0.

Bộ nhớ trong ánh xạ của một process có thể được chia sẻ với ánh xạ trong các process khác. Điều này có thể xảy ra do hai quy trình ánh xạ cùng một vùng của một tệp hoặc bởi vì một process con được tạo ra bởi fork () kế thừa một ánh xạ từ cha của nó. Khi hai hoặc nhiều process chia sẻ cùng một trang, mỗi process có thể thấy các thay đổi được thực hiện bởi các quy trình khác đối với nội dung của các trang, tùy thuộc vào ánh xạ được tạo là riêng tư hay được chia sẻ. Khi ánh xạ là riêng tư,
sửa đổi nội dung của ánh xạ không hiển thị với các process khác và không được chuyển đến tệp cơ sở. Khi ánh xạ được chia sẻ, các sửa đổi đối với nội dung của ánh xạ được hiển thị cho các process khác chia sẻ cùng một ánh xạ và được chuyển tới tệp cơ sở.

Ánh xạ bộ nhớ phục vụ nhiều mục đích khác nhau, bao gồm khởi tạo text segment của process từ segment tương ứng của tệp thi hành, phân bổ bộ nhớ mới (không được lấp đầy), tệp I / O (ánh xạ bộ nhớ I / O ) và truyền thông giữa các proces (thông qua một ánh xạ được chia sẻ).

2.9 Static and Shared Libraries


Thư viện đối tượng là một tệp chứa mã đối tượng được biên dịch cho một (thường là liên quan một cách hợp lý) tập hợp các hàm có thể được gọi từ các chương trình ứng dụng. Tập hợp các hàm trong một thư viện đối tượng đơn giản giúp giảm bớt các tác vụ của việc tạo chương trình và bảo trì. Các hệ thống UNIX hiện đại cung cấp hai loại thư viện đối tượng: thư viện tĩnh và thư viện chia sẻ.

Thư viện tĩnh

Thư viện tĩnh (đôi khi còn được gọi là lưu trữ) là loại thư viện duy nhất trên các hệ thống UNIX đầu tiên. Một thư viện tĩnh về cơ bản là một gói có cấu trúc được biên dịch mô-đun đối tượng. Để sử dụng các hàm từ thư viện tĩnh, chúng ta chỉ định thư viện đó trong lệnh liên kết được sử dụng để xây dựng một chương trình. Sau khi giải quyết các tham chiếu hàm khác nhau từ chương trình chính đến các mô-đun trong thư viện tĩnh, trình liên kết trích xuất các bản sao của các mô-đun đối tượng cần thiết từ thư viện và sao chép vào tập tin thực thi kết quả. Chúng tôi nói rằng một chương trình như vậy được liên kết tĩnh.

Thực tế là mỗi chương trình liên kết tĩnh bao gồm bản sao của đối tượng mô-đun yêu cầu từ thư viện tạo ra một số nhược điểm. Một là việc sao chép mã đối tượng trong các tệp thực thi khác nhau sẽ lãng phí không gian đĩa. Một sự lãng phí bộ nhớ tương ứng xảy ra khi các chương trình liên kết tĩnh sử dụng cùng một chức năng thư viện được thực hiện cùng một lúc; mỗi chương trình yêu cầu riêng của mình bản sao của hàm để nằm trong bộ nhớ. Ngoài ra, nếu một hàm thư viện yêu cầu sửa đổi, sau đó, sau khi biên dịch lại hàm đó và thêm nó vào thư viện tĩnh, tất cả các ứng dụng cần sử dụng chức năng cập nhật phải là relinked lại thư viện.

Thư viện được chia sẻ

Các thư viện chia sẻ được thiết kế để giải quyết các vấn đề với các thư viện tĩnh. Nếu một chương trình được liên kết với một thư viện được chia sẻ, thì thay vì sao chép đối tượng mô-đun từ thư viện vào tệp thực thi, trình liên kết chỉ ghi một bản ghi vào thực thi để chỉ ra rằng tại thời điểm chạy, tệp thực thi cần sử dụng thư viện chia sẻ đó. Khi thực thi được nạp vào bộ nhớ trong thời gian chạy, một chương trình liên kết động được gọi đảm bảo rằng các thư viện được chia sẻ theo yêu cầu của tệp thực thi là tìm thấy và được nạp vào bộ nhớ, và thực hiện liên kết thời gian chạy để giải quyết các cuộc gọi hàm trong tệp thực thi với các định nghĩa tương ứng trong các thư viện được chia sẻ.

Vào thời gian chạy, chỉ một bản sao của mã của thư viện được chia sẻ cần phải được lưu trú trong bộ nhớ; tất cả các chương trình đang chạy đều có thể sử dụng bản sao đó. Thực tế là một thư viện được chia sẻ chứa phiên bản được biên dịch duy nhất của một hàm tiết kiệm dung lượng đĩa. Nó cũng giúp giảm thiểu công việc đảm bảo rằng các chương trình sử dụng phiên bản mới nhất của hàm. Chỉ cần xây dựng lại thư viện được chia sẻ bằng thư viện mới và các chương trình hiện tại tự động sử dụng định nghĩa mới khi chúng được thực thi tiếp theo.

2.10 Interprocess Communication and Synchronization

Một hệ thống Linux đang chạy bao gồm nhiều process, nhiều process hoạt động độc lập với nhau. Tuy nhiên, một số process hợp tác để đạt được mục đích, và các process này cần phương pháp giao tiếp với nhau và đồng bộ hóa hành động của chúng. Một cách để các process giao tiếp là bằng cách đọc và ghi thông tin trong các tệp đĩa. Tuy nhiên, đối với nhiều ứng dụng, đây là khái niệm quá chậm và không linh hoạt.

Do đó, Linux, giống như tất cả các triển khai UNIX hiện đại, cung cấp một tập hợp phong phú các cơ chế cho truyền thông interprocess (IPC), bao gồm:
  • Tín hiệu, được sử dụng để chỉ ra rằng một sự kiện đã xảy ra;
  • Ống (quen thuộc với người dùng shell như toán tử |) và FIFO, có thể được sử dụng để chuyển dữ liệu giữa các quá trình;
  • Socket, có thể được sử dụng để chuyển dữ liệu từ process này sang process khác, trên cùng một máy tính chủ hoặc trên các máy chủ khác nhau được kết nối bởi mạng;
  • Khóa tệp, cho phép quá trình khóa các vùng của tệp để ngăn chặn các quy trình khác đọc hoặc cập nhật nội dung tệp;
  • Hàng đợi tin nhắn, được sử dụng để trao đổi tin nhắn (gói dữ liệu) giữa process;
  • Semaphores, được sử dụng để đồng bộ hóa các hành động của các process; và
  • Chia sẻ bộ nhớ, cho phép hai hoặc nhiều process chia sẻ một phần bộ nhớ. Khi một process thay đổi nội dung của bộ nhớ dùng chung, tất cả process có thể thấy ngay các thay đổi.
Sự đa dạng của các cơ chế IPC trên các hệ thống UNIX, đôi khi bị trùng lặp chức năng, một phần là do sự tiến hóa của chúng dưới các biến thể khác nhau của UNIX hệ thống và các yêu cầu của các tiêu chuẩn khác nhau. Ví dụ, FIFO và socket domain UNIX, về cơ bản thực hiện chức năng tương tự cho phép các quy trình không liên quan trên cùng một hệ thống trao đổi dữ liệu. Cả hai đều tồn tại trong các hệ UNIX hiện đại bởi vì FIFO đến từ System V, trong khi sockets đến từ BSD.

2.11 Signals

Mặc dù chúng tôi liệt kê chúng như một phương pháp của IPC trong phần trước, tín hiệu thường được sử dụng trong nhiều bối cảnh khác, và vì vậy xứng đáng thảo luận lâu hơn. Tín hiệu thường được mô tả là “ngắt” phần mềm, thông báo cho process mà một số sự kiện hoặc điều kiện đặc biệt đã xảy ra. Ở đó là các loại tín hiệu khác nhau, mỗi tín hiệu xác định một sự kiện hoặc điều kiện khác nhau. Mỗi loại tín hiệu được xác định bằng một số nguyên khác nhau, được xác định bằng tên biểu tượng của mẫu SIGxxxx.

Các tín hiệu được gửi tới một process bởi kernel, bởi một process khác (với phù hợp quyền), hoặc bởi chính process đó. Ví dụ, kernel có thể gửi tín hiệu đến một process khi một trong những điều sau xảy ra:
  • Người dùng đã gõ ký tự ngắt (thường là Control-C) trên bàn phím;
  • Một trong những process con đã chấm dứt;
  • Một bộ đếm thời gian (timer) được thiết lập bởi quá trình đã hết hạn; hoặc là
  • Quá trình tìm cách truy cập địa chỉ bộ nhớ không hợp lệ.

Trong trình shell, lệnh kill có thể được sử dụng để gửi tín hiệu tới một tiến trình. kill () gọi hệ thống cung cấp cùng một cơ sở trong chương trình. Khi một process nhận tín hiệu, nó sẽ thực hiện một trong các hành động sau, tùy thuộc vào tín hiệu:
  • Bỏ qua tín hiệu;
  • Bị kill bởi tín hiệu; hoặc là
  • Bị đình chỉ cho đến khi sau đó được tiếp tục bằng cách nhận được một tín hiệu có mục đích đặc biệt.

Đối với hầu hết các loại tín hiệu, thay vì chấp nhận hành động tín hiệu mặc định, một chương trình có thể chọn bỏ qua tín hiệu (hữu ích nếu hành động mặc định cho tín hiệu là gì đó khác hơn là bị bỏ qua) hoặc để thiết lập trình xử lý tín hiệu. Một trình xử lý tín hiệu là một hàm lập trình được định nghĩa được tự động gọi khi tín hiệu là giao cho process. Hàm này thực hiện một số hành động phù hợp với điều kiện tạo ra tín hiệu. Trong khoảng thời gian giữa thời gian được tạo và thời gian được phân phối,
tín hiệu được cho là đang chờ xử lý. Thông thường, tín hiệu đang chờ xử lý được phân phối dưới dạng ngay sau khi process tiếp nhận được lập lịch tiếp theo để chạy, hoặc ngay lập tức nếu process đã chạy. Tuy nhiên, nó cũng có thể chặn một tín hiệu bằng cách thêm nó vào mặt nạ tín hiệu của process. Nếu một tín hiệu được tạo ra trong khi nó bị chặn, nó vẫn còn đang chờ xử lý cho đến khi nó được bỏ chặn sau đó (tức là, bị loại bỏ khỏi mặt nạ tín hiệu).

2.12 Threads

Trong các triển khai UNIX hiện đại, mỗi process có thể có nhiều luồng thực thi. Một cách để xem xét các luồng là một tập hợp các process chia sẻ cùng một bộ nhớ ảo, cũng như một loạt các thuộc tính khác. Mỗi luồng đang thực thi cùng một mã chương trình và chia sẻ cùng một vùng dữ liệu và heap. Tuy nhiên, mỗi luồng có ngăn xếp riêng chứa các biến cục bộ và thông tin liên kết lời gọi hàm. Các luồng có thể giao tiếp với nhau thông qua các biến toàn cục mà chúng chia sẻ. API luồng cung cấp các biến điều kiện và các mutexes, là các nguyên thủy cho phép các luồng của một tiến trình để giao tiếp và đồng bộ hóa chúng. Các hành động, cụ thể là việc sử dụng các biến được chia sẻ của họ. luồng cũng có thể giao tiếp với nhau bằng cách sử dụng IPC và các cơ chế đồng bộ được mô tả trong Mục 2.10.

Ưu điểm chính của việc sử dụng các luồng là chúng giúp bạn dễ dàng chia sẻ dữ liệu (thông qua các biến toàn cục) giữa các luồng hợp tác và một số thuật toán chuyển đổi tự nhiên hơn sang triển khai đa luồng hơn là triển khai đa process. Hơn nữa, một ứng dụng đa luồng có lợi thế của khả năng xử lý song song trên phần cứng đa bộ vi xử lý.

2.13 Process Groups and Shell Job Control


Mỗi chương trình được thực hiện bởi trình shell được bắt đầu trong một process mới. Ví dụ:
shell tạo ra ba process để thực thi đường dẫn lệnh sau (hiển thị danh sách các tệp trong thư mục làm việc hiện tại được sắp xếp theo kích thước tệp):

$ ls -l | sort -k5n | less

Tất cả các shell chính, ngoại trừ shell Bourne, cung cấp một tính năng tương tác được gọi là kiểm soát job, cho phép người dùng thực hiện đồng thời và thao tác nhiều lệnh hoặc đường ống. Trong các trình điều khiển công việc, tất cả các process trong một đường ống là được đặt trong một nhóm process hoặc công việc mới (Trong trường hợp đơn giản của một dòng lệnh shell chứa một lệnh duy nhất, một nhóm process mới chứa chỉ một process đơn lẻ được tạo ra.) Mỗi process trong một nhóm process có cùng một số nguyên nhóm process số nhận dạng, giống như ID tiến trình của một trong các quy trình trong nhóm, gọi là trưởng nhóm. Kernel cho phép các hành động khác nhau, đặc biệt là việc phân phối tín hiệu, được thực hiện trên tất cả các thành viên của một nhóm process. shell điều khiển công việc sử dụng tính năng này để cho phép người dùng tạm ngưng hoặc tiếp tục tất cả các process trong một đường ống, như được mô tả trong phần tiếp theo

2.15 Pseudoterminals

Pseudoterminal là một cặp thiết bị ảo được kết nối, được gọi là master và slave. Cặp thiết bị này cung cấp kênh IPC cho phép truyền dữ liệu cả hai hướng giữa hai thiết bị. Điểm mấu chốt về pseudoterminal là thiết bị slave cung cấp một giao diện hoạt động giống như thiết bị đầu cuối, điều này giúp kết nối thiết bị đầu cuối theo định hướng chương trình để các thiết bị slave và sau đó sử dụng một chương trình khác kết nối với thiết bị master để điều khiển chương trình theo định hướng đầu cuối. Đầu ra được viết bởi trình điều khiển chương trình trải qua quá trình xử lý đầu vào thông thường được thực hiện bởi trình điều khiển thiết bị đầu cuối (ví dụ, trong chế độ mặc định, một carriage return được ánh xạ tới một dòng mới) và sau đó được truyền làm đầu vào cho chương trình định hướng đầu cuối được kết nối với slave. Bất cứ điều gì mà chương trình định hướng đầu cuối ghi vào slave được truyền (sau khi thực hiện tất cả xử lý đầu ra đầu cuối thông thường) làm đầu vào cho chương trình điều khiển. Nói cách khác, chương trình điều khiển đang thực hiện chức năng thường được thực hiện bởi người dùng tại một thiết bị đầu cuối thông thường. Pseudoterminals được sử dụng trong một loạt các ứng dụng, đáng chú ý nhất trong thực hiện các cửa sổ đầu cuối được cung cấp trong đăng nhập Hệ thống Cửa sổ X và trong các ứng dụng cung cấp dịch vụ đăng nhập mạng, chẳng hạn như telnet và ssh.

2.16 Date and Time


Hai loại thời gian được quan tâm đến đối với một process:
  • Thời gian thực được đo hoặc từ một số điểm tiêu chuẩn (thời gian lịch) hoặc từ một số điểm cố định, thường là khởi đầu, trong vòng đời của một quá trình (đồng hồ trôi qua hoặc đồng hồ treo tường) Trên hệ thống UNIX, thời gian lịch được tính bằng giây kể từ nửa đêm vào sáng ngày 1 tháng 1 năm 1970, Thời gian phối hợp toàn cầu (thường là UTC viết tắt) và được phối hợp trên điểm cơ sở cho múi giờ được xác định bởi đường dọc đi qua Greenwich, Anh. Ngày nay, gần với sự ra đời của hệ UNIX, được gọi là Epoch.
  • Thời gian xử lý, cũng được gọi là thời gian CPU, là tổng thời gian của CPU mà một quá trình đã sử dụng kể từ khi bắt đầu. Thời gian CPU được chia thành thời gian CPU hệ thống, thời gian thực hiện mã trong chế độ kernel (tức là, thực hiện lời gọi hệ thống và thực hiện các dịch vụ kernel khác thay mặt cho process), và thời gian CPU của người dùng, thời gian thực hiện mã trong chế độ người dùng (tức là, thực thi mã chương trình bình thường).

Lệnh thời gian hiển thị thời gian thực, thời gian CPU hệ thống và thời gian CPU của người dùng
được thực hiện để thực hiện các quy trình trong một đường ống.

2.17 Client-Server Architecture


Tại nơi khác nhau trong cuốn sách này, chúng ta sẽ thảo luận về thiết kế và thực hiện các ứng dụng client-server. Một ứng dụng client-server là một ứng dụng được chia thành hai quy trình thành phần:
  • Client, yêu cầu server thực hiện một số dịch vụ bằng cách gửi thông điệp yêu cầu ; 
  • Server, kiểm tra yêu cầu của client, thực hiện các hành động thích hợp và sau đó gửi một tin nhắn trả lời lại cho khách hàng.

Đôi khi, client và master có thể tham gia vào một cuộc đối thoại mở rộng các yêu cầu và phản hồi. Thông thường, ứng dụng client tương tác với người dùng, trong khi ứng dụng máy chủ cung cấp quyền truy cập vào một số tài nguyên được chia sẻ. Thông thường, có nhiều các cá thể của các client process liên lạc với một hoặc một vài server process. client và server có thể nằm trên cùng một máy chủ hoặc trên các máy chủ riêng biệt được kết nối qua mạng. Để giao tiếp với nhau, client và server sử dụng các cơ chế IPC được thảo luận trong Phần 2.10. server có thể triển khai nhiều dịch vụ khác nhau, chẳng hạn như:
  • Cung cấp quyền truy cập vào cơ sở dữ liệu hoặc tài nguyên thông tin được chia sẻ khác;
  • Cung cấp quyền truy cập vào tệp từ xa trên mạng;
  • Gói gọn một số logic nghiệp vụ;
  • Cung cấp quyền truy cập vào tài nguyên phần cứng được chia sẻ (ví dụ: máy in); hoặc là
  • Phục vụ các trang web.

Việc đóng gói một dịch vụ trong một máy chủ đơn là hữu ích vì một số lý do, chẳng hạn như sau:
  • Hiệu quả: Có thể rẻ hơn để cung cấp một ví dụ về tài nguyên (ví dụ: máy in) được quản lý bởi máy chủ hơn để cung cấp cùng một tài nguyên cục bộ trên mọi máy tính.
  • Kiểm soát, phối hợp và bảo mật: Bằng cách giữ một tài nguyên (đặc biệt là một nguồn thông tin) tại một địa điểm duy nhất, máy chủ có thể điều phối quyền truy cập vào tài nguyên (ví dụ: để hai khách hàng không đồng thời cập nhật cùng một phần thông tin) hoặc bảo mật thông tin để nó chỉ khả dụng cho các khách hàng được chọn.
  • Hoạt động trong một môi trường không đồng nhất: Trong một mạng, các khách hàng khác nhau, và máy chủ, có thể chạy trên phần cứng và hệ điều hành khác nhau.


2.18 Realtime


Ứng dụng thời gian thực là những ứng dụng cần phản hồi kịp thời để nhập liệu. Thông thường, đầu vào như vậy đến từ cảm biến bên ngoài hoặc thiết bị đầu vào chuyên dụng, và đầu ra có dạng điều khiển một số phần cứng bên ngoài. Ví dụ về ứng dụng với các yêu cầu phản hồi thời gian thực bao gồm đường dây lắp ráp tự động, ATM ngân hàng và hệ thống điều hướng máy bay.

Mặc dù nhiều ứng dụng thời gian thực yêu cầu phản hồi nhanh để nhập, yếu tố xác định là phản hồi được đảm bảo được phân phối trong một thời hạn cuối cùng sau sự kiện kích hoạt. Việc cung cấp đáp ứng thời gian thực, đặc biệt là khi phản hồi ngắn thời gian được yêu cầu, yêu cầu hỗ trợ từ hệ điều hành cơ bản. Phần lớn các hệ điều hành không cung cấp sự hỗ trợ như vậy bởi vì các yêu cầu của đáp ứng thời gian thực có thể xung đột với các yêu cầu của hệ điều hành chia sẻ đa người dùng. Triển khai UNIX truyền thống không phải là thời gian thực hệ điều hành, mặc dù các biến thể thời gian thực đã được đưa ra. Biến thể thời gian thực của Linux cũng đã được tạo và các Linux kernel gần đây đang tiến về phía hỗ trợ đầy đủ  cho các ứng dụng thời gian thực.

POSIX.1b đã xác định một số phần mở rộng cho POSIX.1 để hỗ trợ các ứng dụng thời gian thực. Chúng bao gồm I / O không đồng bộ, bộ nhớ chia sẻ, tệp bộ nhớ ánh xạ, khóa bộ nhớ, đồng hồ thời gian thực và bộ hẹn giờ, chính sách lập lịch thay thế, tín hiệu thời gian thực, hàng đợi tin nhắn và semaphores. Mặc dù chúng không đủ điều kiện làm thời gian thực, hầu hết các triển khai UNIX hiện hỗ trợ một số hoặc tất cả các tiện ích mở rộng này. (Trong quá trình của cuốn sách này, chúng ta mô tả các tính năng của POSIX.1b được hỗ trợ bởi Linux.)

2.19 The /proc File System


Giống như một số triển khai UNIX khác, Linux cung cấp một hệ thống tệp /proc, bao gồm một tập hợp các thư mục và tập tin được gắn trong thư mục /proc. Hệ thống tệp /proc là một hệ thống tệp ảo cung cấp giao diện cho cấu trúc dữ liệu kernel trong một biểu mẫu trông giống như các tệp và thư mục trên một hệ thống tệp. Điều này cung cấp một cơ chế dễ dàng để xem và thay đổi các thuộc tính hệ thống khác nhau.  Ngoài ra, một tập hợp các thư mục có tên của biểu mẫu /proc/PID, trong đó PID là một process ID, cho phép chúng ta xem thông tin về mỗi tiến trình đang chạy trên hệ thống. Nội dung của tệp /proc thường ở dạng văn bản có thể đọc được và có thể được phân tích bằng các kịch bản lệnh shell. Một chương trình có thể chỉ cần mở và đọc từ hoặc viết vào tệp mong muốn. Trong hầu hết các trường hợp, process phải có đặc quyền để sửa đổi nội dung các tệp trong thư mục /proc. Khi chúng ta mô tả các phần khác nhau của giao diện lập trình Linux, chúng ta cũng sẽ mô tả các tệp liên quan /proc. Mục 12.1 cung cấp thêm thông tin chung trên hệ thống tệp này. Hệ thống tệp /proc không được chỉ định bởi bất kỳ tiêu chuẩn nào và các chi tiết mà chúng tôi mô tả là dành riêng cho Linux.

2.20 Kết luận

Trong chương này, chúng ta đã khảo sát một loạt các khái niệm cơ bản liên quan đến lập trình hệ thống Linux. Hiểu biết về các khái niệm này sẽ cung cấp cho người đọc có kinh nghiệm hạn chế trên Linux hoặc UNIX với đủ nền tảng để bắt đầu học lập trình hệ thống.

Nhận xét

Bài đăng phổ biến