Hệ điều hành: Ba phần dễ dàng. Phần 3: API quy trình (bản dịch)

Giới thiệu về hệ điều hành

Này Habr! Theo quan điểm của tôi, tôi muốn giới thiệu với các bạn một loạt bài viết-bản dịch của một nền văn học thú vị - OSTEP. Tài liệu này thảo luận khá sâu về công việc của các hệ điều hành giống unix, cụ thể là làm việc với các tiến trình, các bộ lập lịch khác nhau, bộ nhớ và các thành phần tương tự khác tạo nên một hệ điều hành hiện đại. Bạn có thể xem bản gốc của tất cả các tài liệu ở đây đây. Xin lưu ý rằng bản dịch được thực hiện không chuyên nghiệp (khá tự do), nhưng tôi hy vọng tôi vẫn giữ được ý nghĩa chung.

Phòng thí nghiệm về chủ đề này có thể được tìm thấy ở đây:

Những khu vực khác:

Bạn cũng có thể xem kênh của tôi tại điện tín =)

Báo thức! Có một phòng thí nghiệm cho bài giảng này! Nhìn github

API quy trình

Hãy xem một ví dụ về việc tạo một tiến trình trong hệ thống UNIX. Nó xảy ra thông qua hai cuộc gọi hệ thống cái nĩa() и hành ().

Gọi ngã ba()

Hệ điều hành: Ba phần dễ dàng. Phần 3: API quy trình (bản dịch)

Hãy xem xét một chương trình thực hiện lệnh gọi fork(). Kết quả thực hiện của nó sẽ như sau.

Hệ điều hành: Ba phần dễ dàng. Phần 3: API quy trình (bản dịch)

Trước hết chúng ta nhập hàm main() và in chuỗi ra màn hình. Dòng chứa mã định danh quy trình mà trong bản gốc được gọi là PID hoặc định danh tiến trình. Mã định danh này được sử dụng trong UNIX để chỉ một quy trình. Lệnh tiếp theo sẽ gọi fork(). Tại thời điểm này, một bản sao gần như chính xác của quy trình được tạo ra. Đối với HĐH, có vẻ như có 2 bản sao của cùng một chương trình đang chạy trên hệ thống, do đó sẽ thoát khỏi hàm fork(). Tiến trình con mới được tạo (liên quan đến tiến trình cha đã tạo ra nó) sẽ không còn được thực thi nữa, bắt đầu từ hàm main(). Cần nhớ rằng một tiến trình con không phải là một bản sao chính xác của tiến trình cha; đặc biệt, nó có không gian địa chỉ riêng, các thanh ghi riêng, con trỏ riêng tới các lệnh thực thi và những thứ tương tự. Do đó, giá trị trả về cho người gọi hàm fork() sẽ khác. Cụ thể, tiến trình cha sẽ nhận giá trị PID của tiến trình con dưới dạng trả về và tiến trình con sẽ nhận giá trị bằng 0. Bằng cách sử dụng các mã trả về này, bạn có thể tách các tiến trình và buộc mỗi tiến trình thực hiện công việc riêng của mình. . Tuy nhiên, việc thực hiện chương trình này không được xác định chặt chẽ. Sau khi chia thành 2 quy trình, HĐH bắt đầu giám sát chúng cũng như lên kế hoạch cho công việc của chúng. Nếu được thực thi trên bộ xử lý lõi đơn, một trong các tiến trình, trong trường hợp này là tiến trình cha, sẽ tiếp tục hoạt động và sau đó tiến trình con sẽ nhận được quyền kiểm soát. Khi khởi động lại, tình hình có thể khác.

Gọi chờ()

Hệ điều hành: Ba phần dễ dàng. Phần 3: API quy trình (bản dịch)

Hãy xem xét chương trình sau đây. Trong chương trình này, do sự hiện diện của một cuộc gọi đợi đã() Tiến trình cha sẽ luôn đợi tiến trình con hoàn thành. Trong trường hợp này, chúng ta sẽ nhận được đầu ra văn bản được xác định nghiêm ngặt trên màn hình

Hệ điều hành: Ba phần dễ dàng. Phần 3: API quy trình (bản dịch)

cuộc gọi exec()

Hệ điều hành: Ba phần dễ dàng. Phần 3: API quy trình (bản dịch)

Hãy xem xét thách thức hành (). Cuộc gọi hệ thống này rất hữu ích khi chúng tôi muốn chạy một chương trình hoàn toàn khác. Ở đây chúng ta sẽ gọi executevp () để chạy chương trình wc, một chương trình đếm từ. Điều gì xảy ra khi exec() được gọi? Cuộc gọi này được truyền tên của tệp thực thi và một số tham số làm đối số. Sau đó, mã và dữ liệu tĩnh từ tệp thực thi này được tải và phân đoạn riêng của nó có mã sẽ bị ghi đè. Các vùng bộ nhớ còn lại, chẳng hạn như ngăn xếp và đống, được khởi tạo lại. Sau đó, hệ điều hành chỉ thực thi chương trình, truyền cho nó một tập hợp các đối số. Vì vậy, chúng tôi không tạo quy trình mới, chúng tôi chỉ chuyển đổi chương trình hiện đang chạy sang một chương trình đang chạy khác. Sau khi thực hiện lệnh gọi exec() trong phần con, có vẻ như chương trình gốc hoàn toàn không chạy.

Sự phức tạp khi khởi động này là hoàn toàn bình thường đối với shell Unix và cho phép shell đó thực thi mã sau khi gọi cái nĩa(), nhưng trước cuộc gọi hành (). Một ví dụ về mã như vậy là điều chỉnh môi trường shell theo nhu cầu của chương trình đang được khởi chạy trước khi khởi chạy nó.

Shell - chỉ là một chương trình người dùng. Cô ấy đưa cho bạn dòng giấy mời và đợi bạn viết gì đó vào đó. Trong hầu hết các trường hợp, nếu bạn viết tên của một chương trình vào đó, shell sẽ tìm vị trí của nó, gọi phương thức fork() và sau đó gọi một số loại exec() để tạo một quy trình mới và đợi nó hoàn thành bằng cách sử dụng một cuộc gọi chờ(). Khi tiến trình con thoát, shell sẽ quay trở lại từ lệnh gọi wait() và in lại lời nhắc và đợi lệnh tiếp theo được nhập.

Việc phân tách fork() & exec() cho phép shell thực hiện những việc sau, ví dụ:
tập tin wc > new_file.

Trong ví dụ này, đầu ra của chương trình wc được chuyển hướng đến một tệp. Cách shell đạt được điều này khá đơn giản - bằng cách tạo một tiến trình con trước khi gọi hành (), shell đóng đầu ra tiêu chuẩn và mở tệp tập tin mới, do đó, tất cả đầu ra từ chương trình đang chạy tiếp theo wc sẽ được chuyển hướng đến một tập tin thay vì một màn hình.

Ống Unix được triển khai theo cách tương tự, với điểm khác biệt là chúng sử dụng lệnh gọi pipe(). Trong trường hợp này, luồng đầu ra của quy trình sẽ được kết nối với hàng đợi ống nằm trong kernel, luồng đầu vào của quy trình khác sẽ được kết nối với đó.

Nguồn: www.habr.com

Thêm một lời nhận xét