Tải bản đầy đủ (.pdf) (28 trang)

báo cáo đồ án nguyên lý hệ điều hành

Bạn đang xem bản rút gọn của tài liệu. Xem và tải ngay bản đầy đủ của tài liệu tại đây (888.74 KB, 28 trang )

Báo cáo Đồ án Nguyên lý hệ điều hành

LỜI NÓI ĐẦU
 Nguyên lý hệ điều hành là 1 mơn học bổ ích giúp sinh viên chúng em hiểu biết
được cơ cấu tổ chức, cũng như việc quản lý, điều phối các tiến trình của hệ thống máy
tính. Qua đó hiểu biết phần nào về phần mềm cơ bản nhất của máy tính là hệ điều hành.
Việc nghiên cứu, hoàn thành đồ án nguyên lý hệ điều hành cũng giúp chúng em được
hiểu rõ hơn nữa về hệ điều hành Linux, 1 hệ điều hành có nhiều tính năng vượt trội và có
triển vọng trong tương lai.
Chúng em xin chân thành cảm ơn sự hướng dẫn của thầy Mai Văn Hà, đã tận tình
chỉ dẫn giúp chúng em hồn thành được đề tài đồ án này.

TỔNG QUAN ĐỀ TÀI
1.1. Bối cảnh đề tài:

 Nhận thấy việc giao tiếp giữa các
c ác tiến trình trên linux có
c ó thể
t hể giúp ta
t a hiểu rõ hơn cơ chế
xử lý, và trao đổi dữ liệu giữa các tiến trình trong hệ điều hành linux. Trong đó việc trao
đổi dữ liệu, giao tiếp qua đường ống pipe theo cơ chế FIFO tương đối dễ hiểu và cũng
 phần nào giúp em hiểu được nguyên lý tổ chức hệ điều hành nên chúng em quyết định
chọn đề tài này.
1.2. Mục tiêu đề tài:

Hiểu hơn về việc giao tiếp giữa các tiến trình trong hệ điều hành linux đặc biệt là bằng
đường ống pipe. Nghiên cứu đề tài này sẽ có thể cho ta hiểu được thế nào là pipe và cơ
chế hoạt động của nó.
1.3. Hƣớng giải quyết:


Đầu tiên phải tìm hiểu cách giao tiếp giữa các tiến trình, là thế nào 2 tiến trình giao tiếp
với nhau thơng qua đường ống pipe. Khi giao tiếp bằng pipe này thì vấn đề gì sẽ nảy sinh
ra ( lỗi, hạn chế ). Từ đó tìm ra cách giải quyết và mơ phỏng cách sử dụng pipe thơng qua
 bài tốn.

Khi một pipe được thiết lập giữa hai tiến trình, một tiến trình sẽ ghi dữ liệu vào pipe cịn
tiến trình kia đọc dữ liệu từ pipe.
SVTH: Hoàng Thị Mai Liên

Trang 1


Báo cáo Đồ án Nguyên lý hệ điều hành

Điều trước hết ta phải tạo hai tiến trình cha và con, làm sao để hai tiến trình này giao tiếp
với nhau ta phải tạo một đường ống pipe. Bây giờ chúng ta đi tìm hiểu và tạo các tiến
trình để cho nó giao tiếp với nhau.
1.4.Mơi trƣờng áp dụng:

Chương trình mơ phỏng sẽ được viết bằng ngôn
n gôn ngữ C chạy trên nền hệ điều hành
linux để có thể thấy được cơ chế
c hế giao tiếp tiến trình thơng qua đường ống pipe.
Cộng đồng mã nguồn mở GNU cung cấp
c ấp khá nhiều công cụ biên dịch C/C++ trên
hệ điều hành Linux như:
CodeBlock
GCC
G++
gdb

GNU make
GNU emacs
trình)
Bash
Bison

Trình biên dịc C/C++
Trình biên dịch C
Trình biên dịch C++
Trình gỡ lỗi
Trình quản lý mã nguồn và trợ giúp biên dịch
Trình soạn thảo văn bản (hỗ trợ cho việc sửa mã nguồn khi lập
Hệ vỏ Shell hỗ trợ các dịng lệnh của hệ điều hành
Bộ phân tích tương thích với yacc của UNIX

Trong đồ án mơn học này,
n ày, em sử dụng trình biên dịch CodeBlock 
Code Block chạy
chạy trên nền hệ điều
hành linux Unbuntu 14.04 để áp dụng.

SVTH: Hoàng Thị Mai Liên

Trang 2


Báo cáo Đồ án Nguyên lý hệ điều hành

CƠ SỞ LÝ THUYẾT
1.5.Giới thiệu về hệ điều hành Linux

1.5.1.

Lịch sử ra đời hệ điều hành Linux.

Vào năm 1991 tại Phần Lan, Linux B. Torvalds lúc đó là sinh viên ở trường Đại học tổng
hợp Hensinki đã dùng một máy tính cá nhân có trang bị bộ xử lí 386 để nghiên cứu cách làm
việc của nó. Do hệ điều hành MS-DOS khơng khai thác đầy đủ các đặc tính của bộ xử lí 386,
Linux đã sử dụng một hệ điều hành thương mại khác là Minix. Hệ điều hành Minix là hệ điều
hành Unix cỡ nhỏ.
Do đối mặt với các hạn chế của hệ điều hành này, Linux bắt đầu viết lại một số một số
của phần mềm để thêm chức năng và các điểm đặc trưng. Sau đó, ơng thơng báo kết quả của
mình miễn phí bằng Internet dưới tên gọi Linux – chữ viết tắt của Linus và Unix. Phiên bản đầu
tiên của Linux là 0.01 được tung ra vào tháng 8/1991.
Các phiên bản đầu tiên có rất nhiều hạn chế. Tuy nhiên, sự kiện các mã nguồn được
truyền bá rộng rãi đã giúp phát triển hệ điều hành rất nhanh. Nhiều năm qua, số lượng các công
ty khai thác đã không ngừng tăng lên. Ngày nay, Linux được phát triển bởi nhiều người rải rác
khắp nơi trên thế giới.
Mặc dù 5 phiên bản đầu tiên của Linux tương đối không ổn định, nhưng phiên bản đầu
tiên được tuyên bố là ổn định (1.0 ) đã được công bố vào khoảng tháng 3/1994. Số phiên bản đi
kèm với kernel có một ý nghĩa đặc trưng bởi vì nó liên quan đến chu kì phát triển. Thực tế, quá
trình phát triển Linux diễn ra theo một chuỗi hai giai đoạn:
Giai đoạn phát triển: ở đây kernel khơng có độ tin cậy cao và tiến trình là bổ sung chức
năng cho nó, tối ưu hóa nó và thử nghiệm các ý tưởng mới. Giai đoạn này đem lại sự gia tăng số
lượng các phiên bản đánh số lẻ, chẳng hạn như 1.1, 1.3… Đây là thời điểm mà lượng công việc
tối đa được thực hiện trên kernel.
Giai đoạn ổn định: mục đích là tạo ra một kernel càng ổn định càng tốt. Trong trường
hợp này, chỉ cho phép thực hiện các hiệu chỉnh, sửa đổi nhỏ. Số phiên bản của các kernel được
gọi là ổn định là các số chẵn, chẳng hạn 1.0 , 1.2 và mới nhất là 2.2.
 Ngày nay, Linux hoàn tồn là một hệ điều hành Unix. Nó ổn định và liên tục phát triển.
 Nó khơng chỉ có khả năng phát triển trên các thiết bị ngoại vi mới nhất trên thị trường ( bộ nhớ

flash quang , đĩa quang … ) mà hiệu năng của nó cịn có thể so sánh với một số hệ điều hành
Unix thương mại và thậm chí cịn có một số điểm ưu việt hơn. Sau cùng, mặc dù Linux đã có
một khoảng thời gian bị giới hạn trong môi trường các trường đại học, bây giờ nó đang được tiếp
nhận ở các hãng công nghiệp.
.
1.5.2. Các chức năng của hệ điều

hành Linux

Hệ điều hành Linux có rất nhiều chức năng và chúng khai thác khả năng của các hệ Unix hiện
đại theo các cách sau :
Đa xử lí: các bộ đa xử lí có thể thực hiện nhiều chương trình đồng thời bất kể sử dụng một hay
nhiều bộ xử lí.
SVTH: Hồng Thị Mai Liên

Trang 3


Báo cáo Đồ án Nguyên lý hệ điều hành
Đa nền: Cho phép nhiều người sử dụng : giống như tất cả các hệ Unix, Linux cho phép nhiều
người sử dụng cùng làm việc trên một máy ở cùng thời điểm.
Hỗ trợ truyền thơng giao xử lí ( Pipes , IPC , Sockets ).
Quản lí các thơng điệp điều khiển khác nhau.
Hệ thống quản lí thiết bị đầu cuối tuân thủ theo tiêu chuẩn POSIX. Linux cũng giả các
thiết bị đầu cuối cũng như điều khiển quá trình.
Hỗ trợ một dải rộng các thiết bị ngoại vi, chẳng hạn như các card âm thanh, giao diện đồ
hoa, mạng, giao diện hệ máy tính nhỏ ….
Buffer cache : vùng bộ nhớ được dành để làm vùng đệm cho các đầu vào và đầu ra từ các
q trình khác nhau.


Hệ thống quản lí bộ nhớ trang yêu cầu. Một trang sẽ không được nạp chừng nào nó
khơng thực sự cần thiết ở bộ nhớ.
Các thư viện động và dùng chung : Các thư viện động chỉ được tải khi chúng thật sự cần
thiết và mã của chúng được dùng chung nếu nhiều ứng dụng đang dùng chúng.
Các hệ thống file có thể quản lí tốt và đồng đều các phân hoạch file Linux được sử dụng
 bởi filesystem làm các phân hoạch có các định dạng khác ( MS-DOS , ISO9660, vv. .. ).
Thiết bị của TCP/IP và các giao thức mạng khác.
1.5.3.

Hạn chế của hệ điều hành Linux:

Hiện nay hệ điều hành Linux vẫn còn rất hạn chế đối với người sử dụng, nó vẫn chưa
được sử dụng một cách rộng rãi. Một phần là do có q ít phần mền tương thích với nó.
1.6. Tiến trình trong Linux:

Tiến trình là một chương trình đang xử lý, sở hữu một con trỏ lệnh, tập các thanh ghi và
các biến. Để hoàn thành tác vụ của mình, một tiến trình có thể cần đến một số tài nguyên – như
CPU, bộ nhớ chính, các tập tin và thiết bị nhập/xuất.
Cần phân biệt 2 khái niệm chương trình và tiến trình. Một chương trình là một thực thể thụ
động, chứa đựng các chỉ thị điều khiển máy tính để tiến hành một tác vụ nào đó.  Khi cho thực
hiện các chỉ thị này, chương trình chuyển thành tiến trình, là một thực thể hoạt động, với con trỏ
lệnh xác định chỉ thị kế tiếp sẽ thi hành, kèm theo tập các tài nguyên phục vụ cho hoạt động của
tiến trình.
Về mặt ý niệm, có thể xem như mỗi tiến trình sỡ hữu một bộ xử lý ảo cho riêng nó,
nhưng trong thực tế, chỉ có một bộ xử lý thật sự được chuyển đổi qua lại giữa các tiến trình. Sự
chuyển đổi nhanh chóng này được gọi là sự đa chương (multiprogramming). Hệ điều hành chịu
trách nhiệm sử dụng một thuật toán điều phối để quyết định thời điểm cần dừng hoạt động của
tiến trình đang xử lý để phục vụ một tiến trình khác, và lựa chọn tiến trình tiếp theo sẽ được phục
vụ. Bộ phận thực hiện chức năng này của hệ điều hành được gọi là bộ điều phối (scheduler)


SVTH: Hoàng Thị Mai Liên

Trang 4


Báo cáo Đồ án Nguyên lý hệ điều hành
1.6.1.

Các trạng thái của tiến trình

Trạng thái của tiến trình tại một thời điểm được xác định bởi hoạt động hiện thời
của tiến trình tại thời điểm đó. Trong q trình tồn tại, một tiến trình thay đổi trạng thái
do nhiều nguyên nhân như : phải chờ một sự kiện nào đó xảy ra, hay đợi một thao tác
nhập/xuất hoàn tất, buộc phải dừng hoạt động do đã hết thời gian xử lý …
Tại một thời điểm, một tiến trình có thể nhận trong một các trạng thái sau đây  :
 I n execution ( thực hiện ) : Tiến trình đang được bộ xử lý thực hiện.
 Ready ( sẵn sàng ): Tiến trình có thể được thực hiện nhưng một tiến trình khác
lại đang chạy.
 Suspended (chờ kích hoạt): Tiến trình chờ diễn ra sự kiện ( Ví dụ như đang chờ
nhập hoặc xuất kết thúc).
 Zombie (tồn tại) : Tiến trình đã kết thúc thực hiện, nhưng nó vẫn được tham
chiếu trong hệ thống.
 Stop (ngừng) : Tiến trình đã bị treo bởi một tiến trình ngồi
Các thay đổi trạng thái của một tiến trình được trình bày ở sơ đồ trạng thái sau :

Sự
tạo

Tín hiệu


 Ngừng

Tín hiệu

thành

Sẵn sàng

Lập lịch trình

Kết thúc
 Nhập / Xuất

Treo

Đang
thực hiện

Kết thúc
Tồn tại

 Nhập / Xuất

Sơ đồ trạng thái của một tiến trình.
Tại một thời điểm, chỉ có một tiến trình có thể nhận trạng thái Thực hiện trên một
 bộ xử lý bất kỳ. Trong khi đó, nhiều tiến trình có thể ở trạng thái khác.
Các cung chuyển tiếp trong sơ đồ trạng thái biễu diễn những sự chuyển trạng thái có thể
xảy ra trong các điều kiện sau  :
- Tiến trình mới tạo được đưa vào hệ thống
- Bộ điều phối cấp phát cho tiến trình một khoảng thời gian sử dụng CPU

- Tiến trình kết thúc
Hình 1 :

SVTH: Hoàng Thị Mai Liên

Trang 5


Báo cáo Đồ án Nguyên lý hệ điều hành
- Tiến trình yêu cầu một tài nguyên nhưng chưa được đáp ứng vì tài nguyên chưa sẵn

sàng để cấp phát tại thời điểm đó; hoặc tiến trình phải chờ một sự kiện hay thao tác
nhập/xuất.
- Bộ điều phối chọn một tiến trình khác để cho xử lý .
- Tài nguyên mà tiến trình yêu cầu trở nên sẵn sàng để cấp phát ; hay sự kiện hoặc thao
tác nhập/xuất tiến trình đang đợi hồn tất.
1.6.2.

Cấu trúc tiến trình

Một tiến trình được đặc trưng bởi nhiều thuộc tính do hệ thống duy trì như:


Trạng thái của nó.



Định danh của nó.




Các giá trị của các thanh ghi, bao gồm cả bộ đếm chương trình.



Mã định danh người sử dụng có tên mà tiến trình đang thực hiện.



Thông tin được kernel sử dụng để thiết lập lịch biểu của các tiến trình (thứ tự
ưu tiên, v.v…).



Thơng tin về khơng gian địa chỉ của tiến trình ( các phân đoạn của mã, dữ liệu,
ngăn xếp).



Thông tin về các nhập / xuất được tiến trình thực hiện ( các mô tả về các file
mở, thư mục hiện hành …)



Tính tương thích thơng tin tổng kết các tài ngun do tiến trình sử dụng.

Ví dụ, khi có hai người dùng (user), một mang tên là A, một mang tên là B cùng đăng nhập và
chạy chương trình C ( chương trình có nhiệm vụ tìm một chuỗi ký tự trong file) đồng thời, thực
tế hệ điều hành sẽ quản lý và nạp mã của chương trình C vào hai vùng nhớ khác nhau và gọi mỗi
 phân vùng như vậy là tiến trình. Hình cho thấy cách phân chia chương trình C thành hai tiến

trình cho hai người dùng khác nhau sử dụng.
Ở hình minh họa, người dùng A chạy chương trình C tìm chuỗi B trong tệp findgirl.txt 
$CB findA.txt

Trong khi người dùng B chạy C và tìm chuỗi A trong tệp findboy.txt 
$CA findB.txt

Chúng ta nên nhớ rằng hai người dùng A và B có thể ở hai máy tính khác nhau đăng nhập vào
máy chủ Linux và gọi C chạy đồng thời. Hình 2 là hiện trạng khơng gian bộ nhớ hệ điều hành
Linux khi chạy chương trình C phục vụ người dùng.

SVTH: Hoàng Thị Mai Liên

Trang 6


Báo cáo Đồ án Nguyên lý hệ điều hành

 Nếu dùng lệnh ps, hệ thống sẽ liệt kê cho bạn thông tin về các tiến trình mà hệ điều hành đang
kiểm sốt. Ví dụ với tham số -af 
, chúng ta có thể thấy các thông tin do ps liệt kê như sau
UID
root
root
A

PID
2345
4345
111


PPID
1234
2342
1235

C
0
0
0

STIME
Apr11 pts/0
20:44 pts/3
20:44 tty1

TTY TIME
00:00:00
00:00:00
00:00:00

CMD
[bash]
ps – af
CB

1235

0


20:44 tty1

00:00:00

CA

find…
B
222
find..

…..
Mỗi tiến trình được gán cho một định danh để nhận dạng gọi là PID (process identifier). PID
thường là số nguyên dương có giá trị từ 2 – 32768. Khi một tiến trình mới yêu cầu khởi động, hệ
điều hành sẽ chọn lấy một số (chưa bị tiến trình đang chạy nào chiếm giữ) trong khoản số
nguyên trên và cấp phát cho tiến trình mới. Khi tiến trình chấm dứt, hệ thống sẽ thu hồi lại số
PID để cấp phát cho tiến trình mới.
Tiến trình với PID = 1 là tiến trình init, tiến trình init được gọi và chạy ngay khi khởi động hệ
điều hành, init là tiến trình quản lý và tạo ra mọi tiến trình con khác.
Chúng ta thấy lệnh ps – af hiển thị hai tiến trình A và B với các số PID lần lượt là 111 và 222.
Mã lệnh thực thi của lệnh C chứa trong tệp chương trình có sẵn trong đĩa được hệ điều hành nạp
vào bộ nhớ. Như đã thấy trên hình , mỗi tiến trình được hệ điều hành phân chia rõ ràng : vùng
chứa mã lệnh (code), vùng chứa dữ liệu (data). Mã lệnh thường là giống nhau và có thể sử dụng
chung. Linux quản lý cho phép tiến trình của cùng một chương trình có thể sử dụng chung mã
lệnh của nhau.
Thư viện cũng vậy. Trừ những thư viện đặc thù cịn thì các thư viện chuẩn sẽ được hệ điều hành
cho phép chia sẻ và dùng chung bởi mọi tiến trình trong hệ thống. Việc chia sẻ thư viện, kích
thước chương trình sẽ giảm đi đáng kể.
SVTH: Hoàng Thị Mai Liên


Trang 7


Báo cáo Đồ án Nguyên lý hệ điều hành
Trừ mã lệnh và thư viện cịn hầu như dữ liệu khơng thể chia sẻ bởi các tiến trình. Mỗi tiến trình
sở hữu phân đoạn dữ liệu riêng ( như ta thấy ở ví dụ trên). Mỗi tiến trình cũng được hệ thống
dành riêng cho một bảng mô tả file. Bảng này chứa các số mô tả áp đặt cho các file đang được
mở. Và mỗi tiến trình có riêng ngăn xếp stack để lưu biến cục bộ và các giá trị trả về sau lời gọi
hàm. Tiến trình cũng được dành cho khoảng không gian dành riêng để lưu các biến môi trường.
Cuối cùng mỗi tiến trình hoạt động trong khơng gian địa chỉ ảo độc lập do hệ thống cấp phát.
Chúng ta không cần quan tâm đến bộ nhớ vật lý. Linux và UNIX có cách quản lý phân tran g
cộng với hoán chuyển giữa bộ nhớ vật lý và bộ nhớ ảo thơng qua phân vùng swap.
Bảng thơng tin tiến trình
Hệ điều hành lưu dữ một cấu trúc danh sách bên trong hệ thống gọi là bảng tiến trình(process
table).Bảng tiến trình quản lý tất cả các Pid của hệ thống cùng với các thơng tin chi tiết về các
tiến trình đang chạy.
Ta có thể xem bảng tiến trình với lệnh ps – af ( với tùy chọn –af để hiển thị chi tiết ) :
UID
root
A

PID
2345
111

PPID
1234
1235

C

0
0

STIME
Apr11
20:44

222

1235

0

20:44

TTY
pts/0
tty1

TIME
00:00:00
00:00:00

CMD
[bash]
CB

tty1

00:00:00


CA

find…
B
find..

…..
Trong đó :


UID : tên người dùng đã gọi tiến trình.



PID : là số định danh mà hệ thống cấp cho tiến trình ( được dùng làm khóa chính và đánh chỉ
mục để truy xuất thông tin).



PPID : parent PID là số định danh của tiến trình cha.



STIME : Thời điểm tiến trình được đưa vào sử dụng.
TTY : Là màn hình terminal ảo nơi gọi thực thi tiến trình



TIME : là thời gian chiếm dụng CPU của tiến trình




CMD
1.6.3.

:



tồn

bộ

dịng

lệnh

khi

tiến

trình

được

triệu

gọi


Tạo lập tiến trình

Ta có thể gọi một chương trình khác bên trong chương trình đang chạy bằng hàm system().
Hay nói cách khác ta có thể tạo ra một tiến trình mới từ một tiến trình đang chạy. Hàm system()
được khai báo như sau :
Hàm này gọi chuỗi lệnh cmdstr thực thi và chờ lệnh chấm dứt mới quay về nơi gọi hàm.
#include <stdlib.h>
int system( const char *cmdstr);

SVTH: Hoàng Thị Mai Liên

Trang 8


Báo cáo Đồ án Nguyên lý hệ điều hành
 Nó tương đương với việc ta gọi shell thực thi lệnh của hệ thống.
system() sẽ trả về mã lỗi 127 nếu như không thể khởi động shell
để gọi lệnh cmdstr . Mã lỗi –1 nếu gặp các lỗi khác. Ngược lại mã
trả về của system() là mã lỗi do cmdstr sau khi thực thi trả về.
Ví dụ cách sử dụng hàm system() trong chương trình:
$sh –c cmdstr

Ví dụ : system.c
Hàm system() đã được sử dụng để gọi lệnh “ ps – ax” của hệ điều hành.
#include <stdlib.h>
#include<stdio.h>
int main()
{
printf(“Running ps with system\n”);
system(“ps –ax”);

printf(“Done.\n”);
exit(0);
}

Hàm system() là cách đơn giản nhất để ta triệu gọi một chương trình n gồi khác. Hay nói

đúng hơn là tạo một tiến trình mới tách biệt với tiến trình ban đầu của nơi gọi.
1.6.4.

Thay thế tiến trình hiện hành với hàm exec

Mỗi tiến trình được hệ điều hành cấp cho một không gian nhớ riêng biệt để tiến trình hoạt
động. Nếu tiến trình A gọi một chương trình ngồi B (bằng hàm system() chẳng hạn) hệ điều
hành thường thực hiện các thao tác như : cấp phát khơng gian bộ nhớ cho tiến trình mới, điều
chỉnh lại danh sách các tiến trình, nạp mã lệnh của chương trình B trên đĩa vào khơng gian nhớ
vừa cấp phát cho tiến trình. Đưa tiến trình vào danh sách cần điều phối của hệ điều hành. Những
công việc trên đòi hỏi thời gian và chiếm tài nguyên của hệ thống.
 Nếu tiến trình A đang chạy và ta muốn tiến trình B khởi động chạy trong khơng gian bộ nhớ đã
cấp sẵn cho tiến trình A : Linux cung cấp cho ta tập hợp các hàm exec() thực hiện chức năng khá
thú vị này. Hàm exec() sẽ thay thế tồn bộ ảnh của tiến trình A (bao gồm mã lệnh, dữ liệu, bảng
mô tả file) thành ảnh của tiến trình B hồn tồn khác. Chỉ có số định danh PID của tiến trình A là
cịn giữ lại. Hàm thay thế ảnh của tiến trình bao gồm tập các hàm sau :
Đa số các hàm này đều yêu cầu bạn chỉ đối số path hoặc file là đường dẫn đến chương trình cần
#include<unistd.h>
extrn char **environ;
int execl(const char *path, const char *arg,…);
int execlp(const char *file, const char *arg…);
int execle(const char *path, const char *arg,…,char
int exect(const char *path, const char *argv[]);
int execv(const char *path, const char *argv[]);

int execvp(const char *path, const char *argv[]);

*const envp[]);

thực thi trên đĩa. Arg là các đối số cần truyền cho chương trình thực thi, những đối số này tương
tự cách ta gọi chương trình từ dịng lệnh..
SVTH: Hoàng Thị Mai Liên

Trang 9


Báo cáo Đồ án Nguyên lý hệ điều hành
Ví dụ :  exec.c
Khi chạy chương trình lệnh ps được thực thi, chương trình exec của ta khơng chờ ps
#include <unistd.h>
#include <stdio.h>
int main()
{

 printf(“Running ps with exe clp\n”);
execlp(“ps”, “ps”, “-ax”, 0);
 printf(“Done. – But you never see this line”);
exit();
}

chấm dứt và cũng không bao giờ in ra chuỗi Done. Lý do là toàn bộ mã lệnh và khơng gian của
tiến trình exec sau khi gọi hàm execlp đã bị thay thế bởi tiến trình mới ps.
1.6.5.

Nhân bản tiến trình với hàm fork()


Tiến trình hiện hành có thể tạo ra một tiến trình con nhờ sử dụng lệnh gọi hệ thống fork().
 Nguyên bản của nó là làm nhân bản tiến trình hiện hành> Được khai báo như sau :
#include <sys/types.h>
#include<unistd.h>

 pid_t fork(void);

Khi fork được gọi, tiến trình hiện hành sẽ được nhân bản : bản sao sẽ tuân thủ bản gốc,

ngồi trừ định danh của nó.
Tiến trình ban đầu gọi là tiến trình cha trong khi tiến trình mới gọi là tiến trình con.
 Ngồi PID ra, tiến trình con còn mang thêm một định danh PPID là số định danh PID của tiến
trình cha.

Khởi tạo tiến trình
chính

Gọi fork()
Trả về PID của tiến trình
cha

Mã lệnh kế tiếp
của tiến trình đầu
(cha)

Trả về trị 0

Mã lệnh thực thi tiến
trình mới (con)


Hình 3: Cơ chế phân chia tiến trình của fork()
SVTH: Hồng Thị Mai Liên

Trang 10


Báo cáo Đồ án Nguyên lý hệ điều hành
Trường hợp không tách được, fork() sẽ trả về trị -1. Kiểu pid_t được khai báo và định danh trong
unistd.h là kiểu nguyên(int).
Cả hai tiến trình trên hoạt động đồng thời và có thể đang xen nhau.
Ví dụ cho cách sử dụng hàm fork() để nhân đơi tiến trình:

#include <sys/types.h>
#include <unistd.h>
#include <stdio.h>
int main()
{
pid_t pid;
printf(“fork program starting\n”);
pid = fork();
if (pid == -1)
perror(“Can not call fork”); // Dữ liệu bị lỗi
else if (pid ==0) printf(“This is the child”);
else printf(“this is the parent”);
}

1.6.6.

Kiểm soát và đợi tiến trình con


Khi fork() tách tiến trình chính thành hai tiến trình cha và con, trên thực tế cả tiến trình cha

lẫn tiến trình con đều hoạt động độc lập. Để có trật tự hơn, ta muốn khi tiến trình cha kết thúc thì
tiến trình con cũng phải hồn tất, hay hơn nữa là tiến trình con phải được thực hiện xong rồi mới
đến tiến trình cha. Hàm wait() sẽ giúp ta thực hiện công việc này. Wait() được khai báo như
sau :

#include <sys/types.h>
#include <sys/wait.h>
pid_t wait(int &stat_loc);

Hàm wait() được gọi trong tiến trình cha sẽ dừng lại chờ tiến trình con kết thúc trước khi thực
hiện tiếp các lệnh điều khiển trong tiến trình cha. wait() làm cho sự liên lạc giữa tiến trình cha và

tiến trình con trở nên tuần tự. Khi tiến trình con kết thúc hàm sẽ trả về số PID tương ứng của tiến
trình con. Nếu ta truyền thêm đối số stat_loc khác Null cho hàm thì wait() cũng sẽ trả về trạng
thái mà tiến trình con kết thúc trong biến stat_loc. Ta có thể sử dụng các macro khai báo sẵn
trong sys/wait.h để diễn dịch ý nghĩa trạng thái như sau :
WIFEXITED(stat_loc)
WEXITSTATUS(stat_loc)
WIFSIGNALED(stat_loc)

SVTH: Hoàng Thị Mai Liên

Trả về trị khác 0 nếu tiến trình con kết
thúc bình thường
Trả về mã lỗi của tiến trình con
Trả về trị khác 0 nếu tiến trình con kết
thúc bởi một tín hiệu gửi đến nhưng

khơng đón bắt và xử lý được.
Trang 11


Báo cáo Đồ án Nguyên lý hệ điều hành
WTERMSIG(stat_loc)

Cho biết số tín hiệu đã hủy tiến trình
con

WIFSTOPPED(stat_loc)
WSTOPSIG(stat_loc)

1.6.7.

Trả về trị khác 0 nếu tiến trình con đã
dừng
Trả về số hiệu của signal

Đón xử lý tín hiệu khi tiến trình con kết thúc

Tiến trình cha có thể đón bắt tình huống hay tín hiệu SIGCHILD khi tiến trình con
chấm dứt và gán cho nó một tác vụ thự thi nào đó. Bằng cách này, tiến trình cha có thể
khơng cần phải gọi wait() để chờ tiến trình con kết thúc mới có thể thực hiện được tác vụ
tiếp theo. Khi tạo tiến trình con xong, tiến trình cha tiếp tục cơng việc của mình. Khi tiến
trình con chấm dứt và gửi tín hiệu đến tiến trình cha, hàm hay bộ xử lý tín hiệu của tiến
trình cha sẽ được triệu gọi thực thi.
Hàm catch_child() được dùng để xử lý tín hiệu(cịn gọi là bộ xử lý tín hiệu). Để
gán một hàm xử lý tín hiệu cho một tín hiệu cụ thể nào đó, ta gọ hàm hệ thống signal().
1.6.8.


Bỏ rơi tiến trình con

 Như ta đa biết, lệnh fork() tách đơi một tiến trình thành tiến trình cha và tiến trình
con. Cả hai tiến trình cùng hoạt động độc lập và hầu như khơng phụ thuộc lẫn nhau. Ta
có thể dùng hàm wait() để lấy thông tin và kiểm tra trạng thái kêt thúc của tiến trình con.
Tuy nhiên nếu tiến trình cha kết thúc trước, tiến trình con vẫn cịn hoạt động, đến khi kết
thúc, nó sẽ khơng cịn điểm trở về để liên hệ với tiến trình cha đa sinh ra nó trước đó.
 Những tiến trình con như vậy gọi là zombie( dỡ dang). Hệ điều hành khi đó sẽ gán PPID
của tiến trình con về 1. Có nghĩa là nếu tiến trình cha của tiến trình con mất đi thì tiến
trình con sẽ được chuyển giao cho tiến trình thủy tổ cao nhất, đó là tiến trình init.
Ta nên kiểm sốt tiến trình con của mình do fork() sinh ra. Cố gắng đừng để tiến
trình con ở trạng thái bị bỏ rơi. Mặc dù tiến trình init có cách thu lượm các tiến trình con
 bị bỏ rơi, nhưng bản thân của tiến trình cũng chiếm một phần thời gian và tài nguyên của
hệ thống.
1.7.Giao tiếp tiến trình
1.7.1.

Tổng quan về giao tiếp giữa các tiến trình
Trong hệ thống các tiến trình khơng phải chạy hồn tồn độc lập với nhau, nhưng

nó có thể giao tiếp với nhau, liên lạc với nhau.

SVTH: Hoàng Thị Mai Liên

Trang 12


Báo cáo Đồ án Nguyên lý hệ điều hành


Chia sẻ thông tin: Nhiều tiến trình có thể cùng quan tâm đến những dữ liệu nào đó,
do vậy hệ điều hành cần cung cấp một môi trường cho phép sự truy cập đồng thời đến các
dữ liệu chung.
Hợp tác hoàn thành tác vụ:  Nhiều tiến trình cùng tham gia thực hiện một cơng
việc. Hình thức này sẽ đẩy nhanh khả năng xử lý công việc trong hệ thống.
1.7.2.

Các cơ chế giao tiếp tiến trình trong Linux

Hệ điều hành Linux cung cấp cho người dùng một số cơ chế giao tiếp giữa các tiến
trình như là:
- Giao tiếp bằng tín hiệu (signals handing).
- Giao tiếp bằng đường ống (pipe).
- Giao tiếp bằng hàng đợi thông điệp (message queue).
- Giao tiếp bằng sử dụng vùng nhớ chia sẻ (share memory).
- Giao tiếp bằng đồng bộ tín hiệu semephore.
- Giao tiếp trao đổi thông qua socket.
1.7.3.

Các vấn đề nảy sinh trong giao tiếp giữa các tiến trình

Do mỗi tiến trình sở hữu một khơng gian địa chỉ riêng biệt, nên các tiến trình khơng thể
liên lạc trực tiếp dễ dàng mà phải nhờ vào các cơ chế do hệ điều hành cung cấp. Khi cung
cấp cơ chế liên lạc cho các tiến trình, hệ điều hành thường phải tìm giải pháp cho các vấn
đề chính yếu sau:
- Giao tiếp tường minh hay tiềm ẩn (explicit naming/ implicit naming): tiến trình có cần
 phải biết tiến trình nào đang trao đổi hay chia sẻ thơng tin với nó? Mỗi liên kết được gọi
là tường minh khi được thiết lập rõ ràng, trực tiếp giữa các tiến trình và là tiềm ẩn khi các
tiến trình liên lạc với nhay thơng qua một quy ước ngầm nào đó.
- Giao tiếp theo chế độ đồng bộ hay không đồng bộ (blocking/ nonblocking): Khi tiến

trình trao đổi thơng tin với một tiến trình khác, các tiến trình có cần phải đợi cho thao tác
liên lạc hoàn tất rồi mới tiếp tục các xử lý khác? Các tiến trình liên lạc theo cơ chế đồng
 bộ sẽ chờ nhau hồn tất việc liên lạc, cịn các tiến trình liên lạc theo cơ chế khơng đồng
 bộ thì khơng.
- Giao tiếp giữa các tiến trình trong hệ thống tập trung và hệ thống phân tán: Cơ chế liên
lạc giữa các tiến trình trong cùng một máy tính có sự khác biệt so với việc liên lạc giữa
các tiến trình giữa những máy tính khác nhau.
1.8.Cơ chế giao tiếp
1.8.1.

bằng đƣờng ống (pipe)

Định nghĩa

Các pipe (ống dẫn) tạo thành phương tiện truyền thơng giữa những tiến trình. Sự
truyền dữ liệu giữa các tiến trình được thực hiện thơng qua một lệnh truyền : dữ liệu đã
ghi tại một đầu của kênh được đọc ở đầu bên kia.
SVTH: Hoàng Thị Mai Liên

Trang 13


Báo cáo Đồ án Nguyên lý hệ điều hành
Ống dẫn truyền thơng
Tiến trình A

Tiến trình B

Hình 4: Truyền thơng giữa hai tiến trình bằng pipe .


Các pipe khơng cung cấp một truyền thông theo cấu trúc. Thao tác đọc độc lập với

thao tác ghi và do đó tại cấp độ của các lệnh gọi hệ thống, người ta không thể biết được
kích cỡ, người gởi và người nhận dữ liệu chứa trong pipe. Mặt khác, một ưu điểm của
 phương pháp truyền thông này là khả năng sử dụng dữ liệu đã ghi nhiều lần để được đọc
chỉ một lần.
Ta có thể thấy rõ trong việc chuyển dữ liệu trong Linux, ví dụ :
$ps –ax | grep „runner‟
Lệnh trên trước hết thực hiện lệnh ps  – ax để liệt kê chi tiết danh sách của tất cả
những tiến trình đang chạy trong hệ thống. Kết quả tập dữ liệu mà ps – ax trả về sau đó sẽ
được chuyển cho lệnh grep thơng qua đường ống ( | ). Lệnh grep tiếp nhận dữ liệu và
thực hiện tìm chuỗi runner trong dữ liệu do ps gửi sang. Kết quả của tổ hợp lệnh trên là
chỉ có thơng tin về tiến trình mang tên runner đang chạy được in ra chi tiết.
1.8.2.

Đƣờng ống pipe giao tiếp trao đổi dữ liệu một chiều

Hệ thống cung cấp cho ta hàm pipe() để tạo đường ống có khả năng đọc/ghi. Sau
khi đường ống được tạo ra, ta có thể dùng nó để giao tiếp giữa hai tiến trình (thường là
tiến trình cha và tiến trình con sinh ra bởi hàm fork()). Mỗi đường ống khi tạo ra được
kèm theo một số mơ tả để ta có thể truy xuất
Hàm này yêu cầu đối số là mảng filedes bao gồm hai phần tử nguyên dùng để lưu
#include <unistd.h>
int pipe(int filedes[2])

lại số mô tả cho đường ống trả về sau lời gọi hàm. Nếu hàm thành cơng ta có thể dùng hai
số mô tả này để thực hiện thao tác đọc/ghi trên đường ống. Phần tử thứ nhất của mảng
được dùng để đọc, trong khi phần tử thứ hai của mảng được dùng để ghi. Ví dụ về cách
gọi hàm pipe() tạo đường ống.
 Nếu pipe() thực hiện thành công, đường ống sẽ được tạo ra, pipes[0] là số mô tả

/* Trước hết định nghĩa mảng bao gồm hai phần tử để chứa số mô tả đường
ống */
int pipes[2]
/* Thực hiện tạo đường ống pipe */
int rc = pipe (pipes);
if (rc == -1) { /* tạo đường ống bị lỗi*/
perror(“pipe”);
exit(1);
}

SVTH: Hoàng Thị Mai Liên

Trang 14


Báo cáo Đồ án Nguyên lý hệ điều hành

đường ống phía đầu đọc trong khi pipes[1] là số mô tả đường ống phía đầu ghi. Nếu
khơng tạo được đường ống mã lỗi trả về sẽ là – 1.
Trong ví dụ dưới đây, sau khi tạo ra đường ống, ta gọi hàm fork() để tạo ra tiến
trình con. Như ta đã biết, khi nhân đơi tiến trình, bộ mơ tả file của tiến trình cha và tiến
trình con có khả năng kế thừa nhau cho nên pipes[] mở bởi tiến trình cha cũng được nhân
 bản và sao chép sang tiến trình con. Tiến trình cha sẽ đọc dữ liệu nhập vào từ phía người
dùng và ghi vào đường ống trong khi tiến trình con phía bên kia đường ống tiếp nhận dữ
liệu bằng cách đọc từ đường ống và in ra màn hình.
Ví dụ : oneway_pipe.c
#include <stdio.h>
#include <unistd.h>
/* hàm của tiến trình con */
void do_child(int data_pipe[]{

int c;
/*chứa dữ liệu đọc từ tiến trình cha*/
int rc;
/*Lưu trạng thái trả về của read() */
/*Đóng đầu ghi, vì tiến trình con khơng sử dụng*/
close(data_pipe[1]);
/* Tiến trình con sử dụng số mơ tả đường ống phía đầu đọc để đọc dữ liệu
chuyển đến từ tiến trình cha.*/
while ((rc=read(data_pipe[0],&c,1))>0){
putchar(c);
}
/*Dữ liệu trong đường ống đã được đọc xong – thốt khỏi tiến trình con*/
exit(0);
}
/* Hàm xử lý cơng việc của tiến trình cha*/
void do_parent(int data_pipe[])
{
int c;
/*Dữ liệu đọc được do người dùng nhập vào */
int rc;
/*Lưu trị trả về của getchar()*/
/* Trước là đóng đầu đọc vì khơng cần dùng đến*/
close(data_pipe[0]);
/* Nhận dữ liệu do người dùng nhập và ghi vào đường ống */
while ((c=getchar())>0) {
/*ghi dữ liệu vào đường ống */
if (rc == -1) { /* Dữ liệu bị lỗi */
perror(“Parent : pipe write error”);
close (data_pipe[1]);
exit(1);

}
}
/* Đóng đường ống phía đầu ghi để thơng báo phía cuối đường ống dữ
liệu đã ghi hết */
close(data_pipe[1]);
exit(0);
}
/* Chương trình chính*/
int main()
{
int data_pipe[2];
int pid;
/* pid của tiến trình con*/
int rc;
/* lưu mã lỗi trả về */
/*Tạo đường ống */

SVTH: Hoàng Thị Mai Liên

Trang 15


Báo cáo Đồ án Nguyên lý hệ điều hành
rc = pipe(data_pipe);
if (rc == -1) {
perror(“pipe create error”);
exit(1);
}
/* Tách đơi tiến trình con*/
pid = fork();

switch (pid) {
case –1 :
perror (“fork error”);
exit(1);
case 0:
do_child(data_pipe);
default :
doparent(data_pipe);
}
return 0;
}

Khi chạy chương trình, nếu ta nhập dữ liệu vào từ bàn phím, dữ liệu sẽ được nhận
và ghi trả lại bởi tiến trình con.
Chương trình trên thực hiện cơ chế trao đổi đường ống một chiều. Tiến trình cha
chuyển dữ liệu đến tiến trình con bằng cách ghi vào đường ống. Tiến trình con ngược lại
đọc dữ liệu ra để xử lý ở đầu bên kia đường ống. Ở đây phát sinh nhu cầu trao đổi thông
tin, dữ liệu của hai tiên trình. Ta cần sử dụng hai đường ống song song. Tiến trình sẽ
dùng một đường ống để đọc và một đường ống để ghi.
1.8.3.

Đƣờng ống pipe giao tiếp trao đổi dữ liệu hai chiều

Sử dụng cơ chế đường ống giao tiếp hai chiều dễ dàng cho cả hai phía tiến trình
cha và tiến trình con. Tuy nhiên cũng rất dễ gây ra tình trạng tắc nghẽn. Đây là trường
hợp mà cả hai tiến trình đều ở cùng một trạng thái chờ đọc dữ liệu của nhau trong khi
khơng có tiến trình nào ghi vào đường ống. Sau đây là một số trường hợp có thể dẫn đến
tắc nghẽn :
- Cả hai đường ống đều rỗng. Cả hai đường ống đều gọi hàm read() để đọc dữ liệu.
Chúng sẽ trở nên tắc nghẽn. Bởi vì đặc điểm của hàm read() khi đọc dữ liệu từ pipe theo

cơ chế : nếu đường ống rỗng hàm sẽ khóa trong trạng thái chờ (block) cho đến khi dữ liệu
được đổ vào đường ống hay khi đường ống bị đóng lại bởi phía bên ghi.
- Tr ường hợp này phức tạp hơn. Mỗi đường ống sử dụng bộ đệm với kích thước giới hạn
nhằm tăng tốc cho quá trình đọc/ghi. Khi một tiến trình ghi vào đường ống, dữ liệu được
đặt vào vùng đệm của đường ống cho đến khi nó được lấy ra phía đầu đọc. Nếu vùng
đệm này bị đầy, hàm write() của hệ thống sẽ khóa và chờ cho đến khi vùng đệm được lấy
đi bớt dữ liệu. Cách duy nhất để lấy dữ liệu khỏi vùng đệm đó là phía đầu đọc phải gọi
hàm read(). Do đó, nếu cả hai tiến trình cùng ghi dữ liệu cho đến khi vùng đệm q tải
và khơng có bên đường ống nào thực hiện đọc dữ liệu thì ta cũng sẽ rơi vào trạng thái tắc
nghẽn.

SVTH: Hoàng Thị Mai Liên

Trang 16


Báo cáo Đồ án Nguyên lý hệ điều hành
1.8.4.

Pipe đặt tên

Qua ví dụ trên ta thấy đường ống chỉ được tạo ra và truyền dữ liệu giữa hai tiến
trình cha và con. Tiến trình cha gọi fork() để chuyển giao và gắn đường ống vào tiến
trình con, tiến trình con nhận dữ liệu xử lý và chuyển về cho tiến trình cha. Đường ống
tạo ra bằng hàm pipe() được gọi là đường ống vơ danh. Nó chỉ được sử dụng giữa tiến
trình cha và con do ta chủ động tạo ra bằng hàm fork(). Một vấn đề đặt ra là, nếu hai tiến
trình khơng có quan hệ gì với nhau thì có thể sử dụng được cơ chế pipe để giao tiếp với
nhau khơng? Câu trả lời là có, Linux cho phép ta tạo ra các đường ống đặt tên. Những
đường ống mang tên sẽ nhìn thấy và truy xuất được bởi các tiến trình khác nhau.
* Tạo pipe đặt tên với hàm mkfifo()

Đường ống đặt tên còn được gọi với một tên khác là đối tượng FIFO. Thật sự
đường ống mang tên được biểu diễn như là một file trong hệ thống file của Linux. Ta
dùng lệnh mkfifo để tạo file đường ống với tên chỉ định. Trong chương trình, ta dùng
hàm hệ thống mkfifo() hay mknod(). Khai báo hàm mkfifo() :
#include <sys/types.h>
#include <sys/stat.h>
mkfifo(const char *filename, mode_t mode);
Hàm mkfifo() yêu cầu hai đối số, đối số filename là tên của đường ống cần tạo,

mode là chế độ đọc ghi của đường ống.
Việc tạo đường ống đặt tên không liên quan gì đến các tiến trình. Ta có thể tạo ra
 pipe trước khi xây dựng các chương trình sử dụng nó.

SVTH: Hoàng Thị Mai Liên

Trang 17


Báo cáo Đồ án Nguyên lý hệ điều hành

CÀI ĐẶT THUẬT TỐN VÀ TRIỂN KHAI
1.9.Đề tài :

Viết chương trình gốm 2 q trình. Quá trình thứ nhất cho người dùng nhập vào từ bàn
 phím một chuỗi biểu diễn các phép tính gồm các phần tử +, -, (, ). Dộ ưu tiên của các phép tính
trong ngoặc là cao nhất, phép + và – có cùng độ ưu tiên. Ví dụ : 1+7-(8-7+2). Sau đó truyền
chuỗi dữ liệu này sang q trình thứ 2. Q trình thứ 2 thực hiện tính tốn và trả về cho quá trình
thứ nhất để thể hiện cho người sử dụng biết.
1.10.


Mơ tả bài tốn  :


Chúng ta sẽ xây dựng một chương trình đơn giản mơ phỏng sự giao tiếp
giữa 2 tiến trình : tiến trình cha (P0) và tiến trình con(P1) qua cơ chế đường
ống. Tiến trình cha sẽ cho người dùng nhập vào chuỗi biểu thức cần tính và
lưu vào input_pipe. Sau đó tiến trình con sẽ đọc dữ liệu từ input_pipe và xử
lí tính tốn kết quả chuỗi biểu thức, trả kết quả về cho tiến trình cha thơng
qua output_pipe. Tiến trình cha sẽ lấy dữ liệu ra từ output_pipe và hiện thị
cho người dùng biết.

Các hàm,biến quan trọng sử dụng trong chương trình
Dùng 2 đường ống pipe  : input_pipe và output_pipe.






ParentProcess (int input[2], int output[2]) : Hàm bao gồm các cơng việc mà tiến

trình cha thực hiện như nhập chuỗi biểu thức từ bàn phím, ghi vào input_pipe  ;
đọc kết quả xử lý từ output_pipe nhằm hiện thị cho người dùng biết.


ChildProcess(int input[2], int output[2]): Hàm bao gơm các cơng việc mà tiến

trình con thực hiện như đọc dữ liệu từ input_pipe, gọi hàm hauto() để xử lý dữ
liệu rồi ghi dữ liệu vào lại output_pipe.



Hauto(char* ch) : Hàm xử lí xâu biểu thức, phần tách thành dãy hậu tố, sau đó

tính kết quả của biểu thức và ghi vào cuối chuỗi đã nhập


AddChar(char* ch, char kt): Nối 1 kí tự vào cuối xâu ch



AddNumber(char *ch, int n): Đổi số thành kí tự và nối vào cuối xâu ch



inKt(char kt) : in ra kí tự



inNumber(int n): in ra số

SVTH: Hoàng Thị Mai Liên

Trang 18


Báo cáo Đồ án Nguyên lý hệ điều hành
Lựa chọn ngôn ngữ và IDE: Chương trình được code bẳng ngơn ngữ C trên Ubuntu, IDE
được sử dụng là CodeBlock 
1.11.


Chƣơng trình:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/wait.h>
#include <ctype.h>
#include <stdbool.h>
#include <math.h>
#include <signal.h>
#include <sys/types.h>
#include <unistd.h>
void
void
void
void
void
void
void
bool
void
int

ChildProcess(int input[2], int output[2]);
ParentProcess(int input[2], int output[2]);
xuly(char *ch);
AddChar(char *ch, char kt);
AddNumber(char *ch, int n);
inKt(char kt);
inNumber(int n);
isOperator(char ch);

errorBt();
isOther(int i);

void
{

main(void)
pid_t pid;
int input_pipe[2];
int output_pipe[2];
int rc, child_status;

rc = pipe(input_pipe);
if(rc ==-1){
perror("\tmain : input_pipe error");
exit(1);
}

P1 xử lý biểu thức
nhập vào

rc = pipe(output_pipe);
if(rc ==-1){
perror("\tmain : output_pipe error");
exit(1);
}
pid = fork();
if (pid == 0)
ChildProcess(input_pipe, output_pipe);
else{

//wait( &child_status );
ParentProcess(input_pipe, output_pipe);
}

SVTH: Hoàng Thị Mai Liên

Trang 19


Báo cáo Đồ án Nguyên lý hệ điều hành

}
void
{

ChildProcess(int input[2], int output[2])
int
rc;
char
ch[100] = "";
close(input[1]);
close(output[0]);
rc = read(input[0], &ch, 100);
if (rc==-1)
{
perror("Error");
close(input[0]);
close(input[1]);
exit(1);
}

//Ham xu li bieu thuc vao
xuly(ch);
rc = write(output[1], ch, strlen(ch));
if (rc==-1)
{
perror("Error");
close(output[0]);
close(output[1]);
exit(1);
}
close(input[0]);
close(output[1]);

}
void
{

ParentProcess(int input[2], int output[2])
int
char

rc;
c, ch[100] = "";

close(input[0]);
close(output[1]);
printf("\n\tNhap bieu thuc:
"); gets(ch);
rc = write(input[1], ch, strlen(ch));
if (rc==-1)

{
perror("Error");
close(input[0]);
close(input[1]);
exit(1);
}

rc = read(output[0], ch, 100);
if (rc==-1)
{
perror("Error");

SVTH: Hoàng Thị Mai Liên

Trang 20


Báo cáo Đồ án Nguyên lý hệ điều hành
close(output[0]);
close(output[1]);
exit(1);
}
close(input[1]);
close(output[0]);
}
void xuly(char *ch){
char stack_[100] = "", kq[100] = "", tam[100]="";
int i = 0, j = -1, k = -1, temp = 0;
/*
i : chi so vong lap for

j : chi so stack
k : chi so chuoi hau to kq
*/
//Chuyen bieu thuc dang trung to sang hau to
for( ; iswitch(ch[i]){
case '+':
if(k<0) {
stack_[++j] = 'd'; //truong hop nay + la dau
duong
break;
}
if(isOperator(ch[i-1])) {
while(j>=0 && (stack_[j]=='a' || stack_[j]=='d'))
kq[++k] = stack_[j--];
stack_[++j] = 'd'; //truong hop nay + la dau
duong
break;
}
case '-':
if(k<0) {
stack_[++j] = 'a'; //truong hop nay - la dau am
break;
}
if(isOperator(ch[i-1])) {
while(j>=0 && (stack_[j]=='a' || stack_[j]=='d'))
kq[++k] = stack_[j--];
stack_[++j] = 'a'; //truong hop nay - la dau am
break;
}

else{
while(j>=0 && (stack_[j]=='+' || stack_[j]=='-'
|| stack_[j]=='*' || stack_[j]=='/' || stack_[j]=='^' || stack_[j]=='a'
|| stack_[j]=='d') ) kq[++k] = stack_[j--];
stack_[++j] = ch[i];
}
break;
case '*':
case '/':

SVTH: Hoàng Thị Mai Liên

Trang 21


Báo cáo Đồ án Nguyên lý hệ điều hành
if( (k<0) || isOperator(ch[i-1]) ) {
errorBt();//Bieu thuc loi
return;
break;
}
else{
while(j>=0 && (stack_[j]=='*' || stack_[j]=='/'
|| stack_[j]=='^' || stack_[j]=='a' || stack_[j]=='d') ) kq[++k] =
stack_[j--];
stack_[++j] = ch[i];
}
break;
case '^':
if( k<0 || isOperator(ch[i-1]) ) {

errorBt();//Bieu thuc loi
return;
}
else{
while(j>=0 && (stack_[j]=='^' || stack_[j]=='a'
|| stack_[j]=='d'))
kq[++k] = stack_[j--];
stack_[++j] = ch[i];
}
break;
case '(':
stack_[++j] = ch[i];
break;
case ')':
while(j>=0 && stack_[j]!= '(') kq[++k] = stack_[j--];
j--;
break;
default:
if(isdigit(ch[i]) != 0){
temp = temp * 10 + ch[i] - 48;
if(isdigit(ch[i+1])==0) {
kq[++k] = temp;
temp = 0;
}
}
else i = isOther(i);
}
}
while(j>=0){
kq[++k] = stack_[j--];

}
printf("\n\tDang trung to: %s", ch);
printf("\n\tDang hau to: ");
for(i = 0; i <= k; i++){
switch(kq[i]){
case 'd':
inKt('+');
break;
case 'a':
inKt('-');
break;

SVTH: Hoàng Thị Mai Liên

Trang 22


Báo cáo Đồ án Nguyên lý hệ điều hành
case
case
case
case
case

'+':
'-':
'*':
'/':
'^':
inKt(kq[i]);

break;
case 's':
printf("sin");
break;
case 'c':
printf("cos");
break;
default:
inNumber(kq[i]);
}
}
//Tinh gia tri bieu thuc tu bieu thuc hau to
/*
i : chi so vong lap
j : chi so stack
temp : bien tam
*/
i = 0; j = -1; temp = 0;
for( ; i<= k; i++){
switch(kq[i]){
case 'd':
break;
case 'a':
stack_[j]=-stack_[j];
break;
case '^':
//stack_[j-1] = (char)pow((double)stack_[j-1],
(double)stack_[j]);
temp = (char)mu((int)stack_[j-1], (int)stack_[j]);
stack_[--j] = temp;

break;
case '+':
temp = stack_[j] + stack_[j-1];
stack_[--j] = temp;
break;
case '-':
temp = stack_[j-1] - stack_[j];
stack_[--j] = temp;
break;
case '*':
temp = stack_[j] * stack_[j-1];
stack_[--j] = temp;
break;
case '/':
temp = stack_[j-1] / stack_[j];
stack_[--j] = temp;
break;
default:
stack_[++j] = kq[i];

SVTH: Hoàng Thị Mai Liên

Trang 23


Báo cáo Đồ án Nguyên lý hệ điều hành
}
}
if(j!=0) printf("\nTinh toan sai: j = %d",j);
else printf("\n\tKet qua bieu thuc: %d", stack_[0]);

//Bo sung ket qua sau chuoi da nhap
AddChar(ch, '=');
AddNumber(ch, stack_[0]);
}
void

AddChar(char *ch, char kt){
ch[strlen(ch)+1] = ch[strlen(ch)];
ch[strlen(ch)] = kt;

}
void AddNumber(char *ch, int n){
char a[100];
int i = -1;
if(n < 0){
n = -n;
AddChar(ch, '-');
}
do{
a[++i] = n % 10 + 48;
n = n /10;
}while(n);
for( ; i >=0 ; i--){
AddChar(ch, a[i]);
}
}
void

inKt(char kt){
printf("%c", kt);


}
void

inNumber(int n){
char a[100];
int i = 0;
if(n < 0){
n = -n;
inKt('-');
}
do{
a[++i] = n % 10 + 48;
n = n /10;
}while(n);
for( ; i >=0 ; i--){
inKt(a[i]);
}

}

SVTH: Hoàng Thị Mai Liên

Trang 24


Báo cáo Đồ án Nguyên lý hệ điều hành

bool isOperator(char ch){
switch(ch){

case '+':
case '-':
case '*':
case '/':
case '^':
return true;
default:
return false;
}
}
void errorBt(){
printf("\n Bieu thuc nhap vao khong dung, khong the tinh duoc!\n\n");
//exit(0);
}
int isOther(int i){
printf("\n\nisOther.................\n\n");
return i;
}
int mu(int a, int b){
int i = 1, temp = 1;
if(a==0) return 0;
if(b==0) return 1;
for( ; i<=b; i++) temp *= a;
printf("\n\t mu(%d, %d) = %d\n", a, b, temp);
return temp;
}

SVTH: Hoàng Thị Mai Liên

Trang 25



×