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

Xây dựng chương trình producer consumer với bounded buffer

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 (911.26 KB, 29 trang )

DANH MỤC TỪ VIẾT TẮT 4
LỜI NÓI ĐẦU 1
Chương 1: CƠ SỞ LÝ THUYẾT 3
1.1. Tổng quan về tiến trình 3
1.1.1. Khái niệm 3
1.1.3. Đặc điểm của tiến trình 5
1.1.4. Ngữ cảnh của Tiến trình 5
1.1.5. Trạng thái của Tiến trình 6
1.2. Kiểm soát Tiến trình 7
1.2.1. Tạo Tiến trình 7
1.2.2. Dừng một Tiến trình 9
1.2.3. Giao tiếp giữa các Tiến trình 9
1.3. Cơ chế Semaphore 12
1.3.1. Giới thiệu 12
1.3.2. Phân loại 13
1.3.3. Khởi tạo Semaphore 13
1.3.4. Điều khiển Semaphore 14
1.3.5. Thao tác trên Semaphore 16
1.3.6. Các hàm trong Semaphore 17
Chương 2: BÀI TOÁN 18
2.1. Yêu cầu 18
2.2. Các giải pháp 18
2.2.1. Môi trường phát triển 18
2.2.2. Thread 18
2.2.3. Chương trình nguồn 20
2.2.4. Kết quả chương trình 24
2.3. Đánh giá và kết luận 26
TÀI LIỆU THAM KHẢO 26





MỤC LỤC
Nguyên lý hệ điều hành - Xây dựng chương trình producer consumer
với bounded buffer
DANH MỤC TỪ VIẾT TẮT
TT : Tiến trình
GHT : Gọi hệ thống














Trang 1 SVTH: Dương Văn Lương – Nguyễn Đình Tiên

LỜI NÓI ĐẦU
Chắc chắn chúng ta đều nhận thấy được rằng, chính công nghiệp điện toán đã làm thay
đổi cả thế giới về mọi mặt trong tất cả các lĩnh vực như đời sống, văn hóa, chính trị, xã
hội… Và bây giờ chúng ta chắc sẽ không hình dung nổi cuộc sống của chúng ta sẽ như thế
nào nếu không có sự xuất hiện của công nghiệp điện toán.Những chiếc máy tính ngày càng
nhỏ đi về kích thước, xử lý số liệu ngày càng mạnh lên và đặc biệt là giá cả ngày càng hạ.
Vậy làm thế nào để những chiếc máy tính bằng phần cứng đó hoạt động phục vụ cho mọi

nhu cầu công việc, giải trí của chúng ta, chính là nhờ vào hệ điều hành. Hệ điều hành UNIX
ra đời vào những năm đầu thập niên 60 của thế kỉ XX vẫn được dùng nhiều trên thị trường,
đặc biệt là trong lĩnh vực giáo dục. Ngày nay, với những sự tiện dụng và đặc biệt là mã
nguồn mở của nó mà giúp người dùng dể dàng sử dụng và tinh chỉnh hệ thống theo ý thích
của mình. Một số hệ điều hành thuộc tương tự UNIX như LINUX ngày càng được sử dụng
rộng rãi. Vì thế mà việc tìm hiểu về cơ chế hoạt động, cách làm việc của hệ điều hành này là
không thể thiếu đối với những ai học trong ngành công nghệ thông tin.
Vì thế mà chúng em được nhận đề tài liên quan đến hệ điều hành LINUX, đó là chủ đề
tìm hiểu cơ chế đồng bộ và giao tiếp giữa các tiến trình thông qua Semaphore để giải quyết
bài toán sản xuất – tiêu thụ (Producer – consume) với bounded buffer.
Nội dung đề tài: Xây dựng chương trình Producer – Consumer với bounded buffer
- Giới thiệu tiến trình trong Unix, cách tạo tiến trình.
- Giới thiệu sơ lược về semaphore, giao tiếp đồng bộ với semaphore.
- Các hàm xử lý semaphore.
- Tạo tiến trình Producer-Consumer.
- Sử dụng semaphore để truy xuất tài nguyên.
- Xây dựng chương trình và kết quả demo.
Xây dựng chương trình producer-consumer với bounded buffer

Xây dựng chương trình producer-consumer với bounded buffer


Trang 2 SVTH: Dương Văn Lương – Nguyễn Đình Tiên


Chúng em xin chân thành cảm ơn sự hướng dẫn tận tình của thầy NGUYỄN VĂN
NGUYÊN, cảm ơn sự trao đổi, góp ý của các bạn trong lớp đã giúp chúng em hoàn thành đề
tài này.

Với sự hạn chế về mặt kiến thức nên báo cáo của em vẫn còn nhiều thiếu sót và hạn chế,

vậy em rất mong nhận được sự góp ý thêm của quý thầy cô và các bạn.

Đà nẵng, ngày 19 tháng 5 năm 2011

Sinh viên thực hiện

Đình Tiên – Văn Lương
Xây dựng chương trình producer-consumer với bounded buffer


Trang 3 SVTH: Dương Văn Lương – Nguyễn Đình Tiên


Chương 1: CƠ SỞ LÝ THUYẾT
1.1. Tổng quan về tiến trình

1.1.1. Khái niệm

Unix là hệ đa xử lí, tức khả năng thực thi nhiều tác vụ cùng một lúc. Một chương trình
máy tính là một chuỗi các chỉ lệnh (intructions, hay còn gọi là lệnh máy) mà theo đó máy
tính phải thực hiện. Mặt khác tài nguyên máy tính (CPU, bộ nhớ, tệp, các thiết bị ) là hữu
hạn và khi các chương trình chạy thì các chương trình đều có nhu cầu trên các tài nguyên đó.
Để đáp ứng nhu cầu tài nguyên, cần có một sách lược chạy trình thật hiệu quả để đảm bảo
tính đa nhiệm, nhiều người dùng. Cách phổ biến nhất là cấp tài nguyên cho mỗi chương
trình trong một lượng thời gian nhất định, sao cho các chương trình đều có cơ hội thực hiện
như nhau và trong thời gian thực hiện chương trình, cần kiểm soát việc thực hiện đó chặt
chẻ. Để làm điều đó, ta đưa ra một khái niệm gọi là tiến trình (process).
Vậy tiến trình là một thực thể điều khiển đoạn mã lệnh có riêng một không gian địa
chỉ, có ngăn xếp riêng, có bảng chứa các mô tả tập tin đang mở và đặc biệt là có một định
danh PID (Process Identifier) duy nhất trong toàn bộ hệ thống vào thời điểm tiến trình đang

chạy.
1.1.2. Môi trường thực hiện
Việc thực hiện một TT trên Unix được chia ra làm hai mức: user (người dùng) và
kernel (nhân của hệ thống). Khi một TT của user thực hiện một chức năng của nhân (thông
qua gọi hệ thống GHT), chế độ thực hiện của TT sẽ chuyển từ chế độ người dùng (user
mode) sang chế độ nhân của hệ thống (kernel mode): Hệ thống sẽ thực hiện và phục vụ các
yêu cầu của user, trả lại kết quả. Ngay cả khi user tạo ra các yêu cầu không tường minh, thì
hệ thống vẫn thực hiện các kết toán có liên quan tới TT của user, thao tác các ngắt, lập biểu
các TT, quản lí bộ nhớ
Xây dựng chương trình producer-consumer với bounded buffer


Trang 4 SVTH: Dương Văn Lương – Nguyễn Đình Tiên

Kernel mode là một chế độ đặc quyền, trong đó không có giới hạn nào đối với kernel:
kernel sử dụng tất cả các lệnh của CPU, các thanh ghi của CPU, kiểm soát bộ nhớ, liên lạc
trực tiếp với các thiết bị ngoại vi. Kernel tiếp nhận và xử lí các yêu cầu của các TT của user,
sau đó gởi kết quả đến các TT đó.
User mode được hiểu là chế độ thực hiện bình thường của một tiến trình. Trong chế độ
này, có nhiều hạn chế áp đặt lên TT: TT chỉ truy nhập được các lệnh và dữ liệu của nó,
không thể truy nhập lệnh, dữ liệu của kernel và của các TT khác, một số các thanh ghi của
CPU là cấm. Ví dụ: không gian địa chỉ ảo của một TT được chia ra thành miền chỉ truy nhập
được trong chế độ kernel, miền khác ở chế độ user, hay TT không thể tương tác với máy vật
lí, một số lệnh của CPU không được sử dụng, có thể bị ngắt trong bất kì lúc nào. Một TT
trong user mode khi muốn truy nhập tài nguyên, phải thực hiện qua gọi hệ thống (GHT).
Gọi hệ thống (GHT hay gọi thực hiện chức năng hệ thống cung cấp) là quá trình
chuyển thông số (yêu cầu qua tên hay số của các dịch vụ hệ thống) mà TT yêu cầu cho
kernel thực hiện. Trong Unix, việc đó được làm qua một bẫy hệ thống (trap), sau đó kernel
sẽ thực hiện nhu cầu của TT, đôi khi còn nói là: kernel thực hiện TT trên danh nghĩa của
TT, trong môi trường của TT. Kernel không phảI là tập tách biệt của TT chạy song song với

TT người dùng, mà là một phần của mỗi TT người dùng. Văn cảnh trình bày nói “kernel
cung cấp tài nguyên” hay “kernel thực hiện “ có nghĩa là TT đang chạy trong kernel mode
cấp tài nguyên hay TT thực hiện . . .Bản chất của GHT để thực hiện các dịch vụ của kernel
và mã thực thi các dịch vụ đó đã là một phần trong mã của TT người dùng, chỉ khác là mã
đó chỉ chạy trong kernel mode mà thôi.
Ví dụ: shell đọc đầu vào từ thiết bị đầu cuối bằng một GHT, lúc này kernel thực hiện
nhân danh TT shell, kiểm soát thao tác của thiết bị đầu cuối và trả lại cho shell kí tự nhận
được. Shell sau đó chạy trong user mode, thông dịch xâu kí tự và thực hiện các hành vi nhất
định và có thể phát sinh GHT tiếp theo.

Xây dựng chương trình producer-consumer với bounded buffer


Trang 5 SVTH: Dương Văn Lương – Nguyễn Đình Tiên

1.1.3. Đặc điểm của tiến trình
Như đã nói có rất nhiều TT được thực hiện đồng thời trong hệ thống, nhưng kernel cần
lập biểu để đưa vào thực hiện. Mỗi TT chỉ có một TT cha, nhưng có nhiều TT con của nó.
Kernel nhận biết mỗi TT qua số hiệu của TT gọi là số định danh của TT (Procces ID: PID).
PID thường là số nguyên dương có giá trị từ 2-32768 ( số 1 được dành cho tiến trình
init ) Khi một tiến trình mới yêu cầu khởi động, HĐH 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ảng 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 khác trong
lần sau.
Cũng như User, nó cũng có thể nằm trong nhóm. Vì thế để phân biệt, chúng ta nhận
biết thông qua số hiệu nhóm của tiến trình gọi là pgrp.Một số hàm trong C cho phép chúng
ta có thể tương tác với các tiến trình:
 int getpid( ) : Trả về giá trị int là số hiện pid của tiến trình hiện tại.
 int getppid( ): Trả về giá trị int là pid của tiến trình cha của tiến trình hiện tại.
 int getpgrp( ): Trả về giá trị int là số hiệu của nhóm tiến trình .

 int setpgrp( ): Trả về giá trị int là số hiệu của tiến trình mới được tạo ra.
1.1.4. Ngữ cảnh của Tiến trình
Bối cảnh (context) của TT là tập hợp các thông tin mô tả trạng thái của TT đó. Bối
cảnh được định nghĩa bởi mã lệnh, các giá trị của các biến và các cấu trúc tổng thể của user,
giá trị của các thanh ghi của CPU mà TT đang dùng, các giá trị trong proccess table và nội
dung các stack (ngăn xếp) trong user và kernel mode. Phần mã, cũng như các cấu trúc dữ
liệu tổng thể của kernel tuy có chia sẻ cho TT nhưng không thuộc bối cảnh của TT.
Khi thực hiện một TT, ta nói hệ thống được thực hiện trong bối cảnh của TT đó. Khi
kernel quyết định cho chạy một TT khác, kernel sẽ chuyển đổi bối cảnh (switch context) sao
cho hệ thống sẽ thực hiện trong bối cảnh của TT mới. Kernel cho phép chuyển bối cảnh chỉ
dưới những điều kiện nhất định. Khi chuyển bối cảnh, kernel bảo vệ các thông tin cần thiết
Xây dựng chương trình producer-consumer với bounded buffer


Trang 6 SVTH: Dương Văn Lương – Nguyễn Đình Tiên

để khi trở lại TT trước đó, kernel có thể tiếp tục thực hiện TT đó. Tương tự như vậy cũng
xảy ra cho quá trình chuyển từ user mode sang kernel mode và ngược lại. Tuy nhiên chuyển
từ user mode sang kernel mode của một TT là sự thay đổi chế độ thực hiện, chứ không phải
chuyển đổi bối cảnh. Bối cảnh thực hiện của TT không đổi, vì không có sự chuyển đổi thực
hiện TT khác.
1.1.5. Trạng thái của Tiến trình
Cuộc đời của TT có thể phân chia ra các trạng thái, mỗi trạng thái có các đặc tính mô
tả về TT. Tiến trình có một số các trạng thái sau đây:
1. TT đang chạy trong user mode;
2. TT đang chạy trong kernel mode;
3. TT không được thực hiện (không chạy) nhưng sẵn sàng chạy khi bộ lập biểu chọn để
thực hiện. Có rất nhiều TT trong trạng thái này, nhưng scheduler chỉ chọn một TT.
4. TT ngủ (sleeping): TT không thể thực hiện tiếp vì những lí do khác nhau, ví dụ đang đợi
Xây dựng chương trình producer-consumer với bounded buffer



Trang 7 SVTH: Dương Văn Lương – Nguyễn Đình Tiên

1.2. Kiểm soát Tiến trình
1.2.1. Tạo Tiến trình
Tạo ra các TT là một điểm mạnh trong Unix. Vậy tạo ra các TT để làm gì. Người lập
trình luôn quan tâm tới khả năng thực hiện nhiều tác vụ đồng thời trong khi phát triển một
ứng dụng, trong khi đó cũng muốn sử dụng lại các tiện ích, các hàm đã có để nâng cao năng
lực, hiệu quả tính toán của máy tính. Tạo TT là giải pháp đáp ứng yêu cầu trên, bởi vì một
TT được tạo ra sẽ chạy song song với TT đã tạo ra nó. Đó cũng chính là sức mạnh đa nhiệm
mà Unix có. Ta hãy theo dõi quá trình sau:
Khi thực hiện một lệnh máy, còn gọi là command, một qui trình được thực hiện bao
gồm: Giao diện người – máy, shell, nhận lệnh (command) user đưa vào, sau đó shell tạo ra
một TT (con) để thực hiện command đó. Quá trình này xảy ra qua hai bước:

1. Shell tạo ra một TT con để chạy command, tức tạo ra một bối cảnh sao cho lệnh có thể
thực hiện được. Quá trình này thực sự là việc sao chép bối cảnh của TT bố (shell) cho TT
mới (TT con). Như vậy khi thực hiện command thì command này sử dụng các môi trường
của TT bố đã sao chép cho nó. Tuy nhiên không phải tất cả môi trường của TT bố được sao
chép cho TT con, mà chỉ có các phần tổng thể được sao chép, và phần này bao gồm:
- Môi trường xuất (exported environment): -UID, GID của TT bố, các đầu vào/ra chuẩn
(stdin, stdout), các tệp đã mở, thư mục root, thư mục hiện hành, và các thông tin hệ thống
khác, danh sách các biến tổng thể (global variables) và một vài biến trong môI trường riêng
của TT bố cũng có thể xuất cho TT con.
Xây dựng chương trình producer-consumer với bounded buffer


Trang 8 SVTH: Dương Văn Lương – Nguyễn Đình Tiên


- Mã chương trình.

TT Gốc TT tạo mới
Chuyển thông tin từ TT bố cho TT con, các phần fork sao chép
2. Thực hiện command trong bối cảnh của TT mới được tạo (sẽ do scheduler sắp xếp). TT
con sẽ trả lại các kết quả khi kết thúc thực hiện command qua exit().
Sự tạo lập một tiến trình mới thực hiện bằng lệnh gọi hệ thống fork. Fork() cho phép
một tiến trình lập một bản sao của nó, trừ bộ định dạng tiến trình. Tiến trình gốc tự nhân bản
chính nó được gọi là tiến trình bố và bản sao tạo ra được gọi là tiến trình con.
Cú pháp như sau:
#include<sys/types.h>
#include<unistd.h>
pid = fork();
Khi thực hiện xong, hai TT nói trên có hai bản sao user level context như nhau (exported
environment ở mô hình trên), và với các giá trị trả lại pid khác nhau:
- Trong bản thân mã của TT bố, pid là số định danh của TT con;
- Trong bản thân mã của TT con, pid = 0 thông báo kết quả tạo TT là tốt;

Xây dựng chương trình producer-consumer với bounded buffer


Trang 9 SVTH: Dương Văn Lương – Nguyễn Đình Tiên

1.2.2. Dừng một Tiến trình
Lệnh kill của Shell có thể dùng để chấm dứt hoạt động của một tiến trình. ví dụ như khi
muốn dừng tiến trình 234 ta dùng lệnh: kill 234
C cũng có lệnh kill như sau: int kill(pid, sig);
int pid; là dấu hiệu nhận biết của một tiến trình.
int sig; hằng tín hiệu giao tiếp tiến trình.
1.2.3. Giao tiếp giữa các Tiến trình

Trong các hệ thống đa chương, một tiến trình không đơn độc trong hệ thống mà có
thể ảnh hưởng đến các tiến trình khác, hoặc bị các tiến trình khác tác động. Nói các khác,
các tiến trình là thực thể độc lập nhưng chúng cũng có nhu cầu liên lạc với nhau để:
 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ụ : Đôi khi để đạt được một xữ lí nhanh chóng, người
dung phân chia một tác vụ thành các công việc nhỏ để có thể tiến hành song song. Thường
thì các công việc nhỏ này cần hợp tác với nhau để hoàn thành tác vụ ban đầu . Ví dụ như dữ
liệu kiết xuất của tiến trình này là dữ liệu nhập vào của tiến trình kia. Trong trường hợp đó,
hệ điều hành cần cung cấp các cơ chế để các tiến trình có thể trao đổi thông tin với nhau.
HĐH Linux và UNIX cung cấp cho bạn một số cơ chế giao tiếp giữa các tiến trình
gọi là IPC (Inter Process Communication) bao gồm:
 Trao đổi bằng tín hiệu (signals handling)
 Trao đổi qua cơ chế đường ống (pipe)
 Trao đổi thông qua hàng đợi thông điệp (message queue)
 Trao đổi bằng phân đoạn nhớ chung (shared memory segment)
Xây dựng chương trình producer-consumer với bounded buffer


Trang 10 SVTH: Dương Văn Lương – Nguyễn Đình Tiên

 Giao tiếp đồng bộ với semaphore
 Giao tiếp trao đổi thông qua socket
Việc giao tiếp giữa các tiến trình được thực hiện thông qua các tín hiệu chuẫn của hệ
thống. Tín hiệu là một sự ngắt quãng logic được gửi đến các tiến trình bởi hệ thống để thông
báo cho chúng về những sự việc không bình thường trong môi trường hoạt động của chúng
(như lỗi bộ nhớ, lỗi vào ra). Nó cũng cho phép các tiến trình liên lạc với nhau. Một tín hiệu
(trừ SIGKILL) có thể được xem xét theo ba cách khác nhau:
1. Tiến trình có thể được bỏ qua: Ví dụ chương trình có thể bỏ qua sự ngắt quãng của người

sử dụng hệ thống (đó là sự bỏ qua khi một tiến trình đang được sử dụng ở phần nền.
2. Tiến trình có thể được thực hiện: Trong trường hợp này, khi nhận được 1 tín hiệu, việc
thực hiện 1 tiến trình được chuyển về một quy trình do người sử dụng xác định trước, sau
đó trở lại nơi nó bị ngắt.
3. Lỗi có thể được tiến trình trả về sau khi nhận được tín hiệu này.
Xây dựng chương trình producer-consumer với bounded buffer


Trang 11 SVTH: Dương Văn Lương – Nguyễn Đình Tiên

Dưới đây là một số tín hiệu thường gặp:

Xây dựng chương trình producer-consumer với bounded buffer


Trang 12 SVTH: Dương Văn Lương – Nguyễn Đình Tiên

1.3. Cơ chế Semaphore
1.3.1. Giới thiệu
Semaphore là một biến được bảo vệ (hay là một kiểu dữ liệu trừu tượng), tạo thành
một phương pháp để hạn chế truy nhập tới tài nguyên dùng chung trong môi trường đa lập
trình(multiprogramming). Đây là một phát minh của Edsger Dijkstra và được sử dụng lần
đầu tiên trong hệ điều hành THE. Giá trị của semaphore được khởi tạo bằng số các tài
nguyên tương đương được chia sẻ cái mà cài đặt điều khiển. Trong trường hợp đặc biệt, khi
mà chỉ có một tài nguyên tương đương được chia sẻ, semaphore được gọi là semaphore nhị
phân. Trong trường hợp khái quát, semaphore thường được gọi là biến đếm semaphore.
Semaphore chỉ có thể được truy nhập bằng cách sử dụng các toán tử sau:
P(Semaphore S)
{
if S > 0 then S := S – 1 else wait on S) ;

}
V(Semaphore S)
{
if (Có quá trình đang đợi trên S)
then (kích khởi 1 trong các quá trình đó )
S := S + 1;
}
Init(Semaphore S, Integer V)
{ S := V; }
Giá trị của một semaphore là số đơn vị tài nguyên đang ở trạng thái rỗi. (Nếu chỉ có
một tài nguyên, người ta sẽ sử dụng một "semaphore nhị phân" với các giá trị 0 và 1.) Toán
Xây dựng chương trình producer-consumer với bounded buffer


Trang 13 SVTH: Dương Văn Lương – Nguyễn Đình Tiên

tử P đợi (busy waiting) hoặc đang ngủ cho đến khi một tài nguyên hết bận, khi đó, nó lập tức
chiếm quyền sử dụng tài nguyên đó. V là toán tử đảo ngược; nó thả một tài nguyên sau khi
tiến trình đã sử dụng xong tài nguyên đó. Init chỉ được dùng để khởi tạo semaphore trước tất
cả các yêu cầu sử dụng nào. Các toán tử P và V phải có tính chất nguyên tố, nghĩa là không
có tiến trình nào có thể chặn giữa quá trình thực hiện một trong các toán tử này để chạy một
toán tử khác trên cùng một semaphore đó.
Để tránh tình trạng busy-waiting, một semaphore có thể có một cấu trúc hàng đợi
gồm các tiến trình. Nếu một tiến trình thực hiện một thao tác P đối với một semaphore có
giá trị 0, tiến trình này được đưa vào hàng đợi của semaphore. Khi một tiến trình khác dùng
toán tử V để tăng giá trị của semaphore, và có tiến trình nằm trong hàng đợi, thì một tiến
trình trong đó được lấy ra khỏi hàng đợi và tiếp tục chạy.
1.3.2. Phân loại
Có hai loại Semaphore:


- Semaphore nhị phân chỉ có 2 giá trị là 0 và 1. Nếu Semaphore có hai giá trị hay là
semaphore nhị phân thì nó có vai trò như Mutex trong tương tác các luồng. Semaphore nhị
phân thường được khởi tạo tại giá trị 1. Khi tài nguyên đang được sử dụng, luồng truy nhập
gọi P(S) để giảm giá trị của nó về 0, và khôi phục giá trị 1 bằng toán tử V khi tài nguyên sẵn
sàng được được thả ra.
- Semaphore khác có các giá trị nhiều hơn 2.
1.3.3. Khởi tạo Semaphore
Để khởi tạo cho một Semaphore ban đầu chúng ta sử dụng hàm semget().
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
int semget(key_t key, int nsems, int semflg);
Xây dựng chương trình producer-consumer với bounded buffer


Trang 14 SVTH: Dương Văn Lương – Nguyễn Đình Tiên

 key: giá trị key cho IPC object, nếu key=IPC_PRIVATE thì semaphore
tạo ra chỉ được sử dụng trong nội bộ process.
 nsems: số lượng semaphore trong semaphore set, thông thường chỉ cần
dùng 1 semaphore.
 semflag: IPC_CREAT, IPC_EXCL và có thể OR với giá trị ấn định
quyền truy cập (tương tự quyền hạn trên một file).
Ví dụ :
sset1_id=semget(IPC_PRIVATE,1,IPC_CREAT|IPC_EXCL|0600);
sset2_id=semget(12345,1,IPC_CREAT|IPC_EXCL|0666);

 Để tạo ra một tập semaphore duy nhất với mỗi lần gọi thường sử dụng 2 cách:
- Truyền vào thông số IPC_PRIVATE, hệ thống tự giải quyết.
- Sử dụng hàm ftok()

key_t ftok(const char *path, int id)
*Hàm này trả về key duy nhất với 2 thông số truyền vào khác nhau. “path” là đường dẫn đến
một file bất kỳ có quyền read (sử dụng file nào đó chỉ có user owner có quyền read), “id” là
một ký tự nhận dạng nào đó.
*Trường hợp truyền vào cùng một key thì nó sẽ trả về cùng một semaphore (có id giống
nhau).
1.3.4. Điều khiển Semaphore
Để điều khiển các Semaphore chúng ta sử dụng hàm semctl() .
Semctl ( ) : Là hàm lấy hoặc thay đổi thuộc tính của các đối tượng semaphore. Hàm có cấu
trúc như sau:
Int Semctl(int Semid, int Semnum, int command, union semun);
Trong đó :
Xây dựng chương trình producer-consumer với bounded buffer


Trang 15 SVTH: Dương Văn Lương – Nguyễn Đình Tiên

- Semid : Nó gọi hợp lệ một số hiệu của Semaphore.Tức là giá trị được trả lại thông qua hàm
semget() .
- Semnum : Giá trị của Semnum lựa chọn một giá trị bên trong một mảng bởi các chỉ số của
nó (bắt đầu từ 0)
- Command : Lời gọi Command là một trong những cờ điều khiển sau :
 Các thao tác thông thường
 IPC_SET : thiết lập quyền truy cập
 IPC_STAT: lấy thông tin thuộc tính
 IPC_RMID: xoá semaphore set
 Các thao tác trên từng semaphore riêng lẻ
 GETVAL: lấy thông tin thuộc tính
 SETVAL: thay đổi thuộc tính
 GETPID: lấy PID của process vừa truy cập semaphore

 GETNCNT: lấy số process đang đợi semval tăng lên
 GETZCNT: lấy số process đang đợi semval về 0
 Các thao tác trên toàn semaphore set
 SETALL: thay đổi thuộc tính
 GETALL: lấy thông tin thuộc tính
- union Semun là tùy chọn, nó phụ thuộc vào các thao tác yêu cầu.
union semun
{
int val; /* Value for SETVAL */
struct semid_ds *buf; /* Buffer for IPC_STAT, IPC_SET */
Xây dựng chương trình producer-consumer với bounded buffer


Trang 16 SVTH: Dương Văn Lương – Nguyễn Đình Tiên

ushort *array; /* Array for GETALL, SETALL */
} ;

1.3.5. Thao tác trên Semaphore
Hàm Semop( ) thực hiện những thao tác trên tập hợp Semaphore nhằm thay đổi trạng thái
hay nội dung của nó. Nó là hàm có cấu trúc như sau:
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
int semop(int semid, struct sembuf *sops, size_t nsops);
 semid: semaphore set ID do hàm semget() trảvề
 nsops: số semaphore trong tập các semaphore (độ dài mảng sema)
 sops: là cấu trúc sembuf định ra các thao tác cho từng semaphore trong
tập semaphore.
Một cấu trúc sembuf cụ thể là một thao tác của Semaphore nó được định nghĩa bên trong

<sys/sem.h>.
struct sembuf
{
short sem_num; /*chỉ số Semaphore cần edit*/
short sem_op; /* thao tác được thực hiện */
short sem_flg; /* Cờ điều khiển, nếu có */
};
 sem_num: chỉ số của semaphore trong semaphore set, chỉ số này bắt
đầu từ 0
 sem_op: là số nguyên
Xây dựng chương trình producer-consumer với bounded buffer


Trang 17 SVTH: Dương Văn Lương – Nguyễn Đình Tiên

 >0: tăng giá trị semaphore
 <0: giảm giá trị semaphore
 sem_flg:
 IPC_NOWAIT: non-blocking mode
 SEM_UNDO: undo operation
1.3.6. Các hàm trong Semaphore
 Sem_open() : Được sữ dụng để tạo, để mở hay hiệu chỉnh một Semaphore đã tạo sẵn
trước đó.
 Sem_init(
sem_t *sem, int pshared, unsigned int value
) : Khởi tạo một cấu trúc
Semaphore.
 Sem_Close() :Dùng để đóng một semaphore được mở hay là tạo ra.
 Sem_unlink() : Dùng để kết thúc liên lạc của Semaphore. Và xóa bỏ Semaphore khi
tiến trình kết thúc thực thi,

 Sem_destroy(
sem_t * sem
) : Dùng để hủy 1 Semaphore.
 Sem_getvalue(): Dùng để copy giá trị của Semaphore thành 1 số nguyên cụ thể.
 Sem_wait(
sem_t * sem
): Thực hiện giảm value của semaphore xuống
 Sem_port() : Thực hiện việc tăng dần các giá trị của Semaphore.






Xây dựng chương trình producer-consumer với bounded buffer


Trang 18 SVTH: Dương Văn Lương – Nguyễn Đình Tiên

Chương 2: BÀI TOÁN
2.1. Yêu cầu
Xây dựng chương trình producer-consumer với bounded buffer sử dụng
semaphore. Bài toán là mô hình cho các process hợp tác, quá trình sản xuất (producer
process) tạo ra các thông tin để các quá trình tiêu thụ (consumer producer) sử dụng. Sự trao
đổi thông tin thực hiện qua buffer. Producer và consumer phải được đồng bộ hoạt động.
2.2. Các giải pháp
Ta sử dụng 1 tiến trình chính với thread chính làm Producer. Tạo 1 thread làm
Consumer. Product_Value ban đầu là 2, tối đa MAX_VALUE là 3. Semaphore có value ban
đầu là 2, tức là lúc này yêu cầu sử dụng tài nguyên không vượt quá 2. Mỗi khi Producer sản
xuất 1 product thì Consumer lại tiêu thụ 2 product, kết quả product giảm dần đến 0, lúc này

Consumer không thể tiêu thụ được nữa do product đã hết mà phải chờ Producer sản xuất ra.
2.2.1. Môi trường phát triển
- Hệ điều hành: Ubuntu v10.10 final.
- Ngôn ngữ lập trình C trong linux.
- Trình biên dịch GCC.
2.2.2. Thread
- Luồng (Thread) là một phần của tiến trình sở hữu riêng ngăn xếp (stack) và thực thi
độc lập ngay trong mã lệnh của tiến trình.
- Trong mỗi tiến trình có thể tạo ra nhiều luồng hoạt động song song với nhau (như
cách tiến trình hoạt động song song)
- Ưu điểm của luồng là hoạt động trong cùng không gian địa chỉ của tiến trình.
- Khi chương trình chính bắt đầu chính là một luồng (thread)
Xây dựng chương trình producer-consumer với bounded buffer


Trang 19 SVTH: Dương Văn Lương – Nguyễn Đình Tiên

- Luồng điều khiển hàm main() của chương trình được gọi là luồng chính. Các luồng
khác do tiến trình tạo ra được gọi là luồng phụ.
- Tiến trình có định danh là PID, còn luồng cũng có một số định danh được gọi là
thread ID
- Để tạo ra một luồng mới  gọi hàm pthread_create():
#include <pthread.h>
int pthread_create(pthread_t *thread, pthread_attr_t *attr,
void *(*start_routine) (void*), void *argv);

Xây dựng chương trình producer-consumer với bounded buffer


Trang 20 SVTH: Dương Văn Lương – Nguyễn Đình Tiên


2.2.3. Chương trình nguồn
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
#include <errno.h>
#include <unistd.h>
#include <stdio.h>
#include <signal.h>
#include <stdlib.h> //cho lenh exit
#define MAX_PRODUCT 3
int product_val = 2;
int sem;

typedef union
{
int val;
struct semid_ds *buf;
ushort *array;
}semun ;
int seminit()
{
int i, semid;
semun carg;
Xây dựng chương trình producer-consumer với bounded buffer


Trang 21 SVTH: Dương Văn Lương – Nguyễn Đình Tiên

if (semid=semget(IPC_PRIVATE,1,0666|IPC_EXCL)==-1) return(-1);

carg.val=2;
if (semctl(semid,0,SETVAL,carg)==-1) return(-1);
return semid;
}

void P(int sem)
{
struct sembuf pbuf;
pbuf.sem_num=0;
pbuf.sem_op=-1;
pbuf.sem_flg=SEM_UNDO;
if (semop(sem,&pbuf,1)==-1)
{
perror("semop");
exit(1);
}
}
void V(int sem)
{
struct sembuf vbuf;
vbuf.sem_num=0;
vbuf.sem_op=1;
Xây dựng chương trình producer-consumer với bounded buffer


Trang 22 SVTH: Dương Văn Lương – Nguyễn Đình Tiên

vbuf.sem_flg=SEM_UNDO;
if (semop(sem,&vbuf,1)==-1)
{

perror("semop");
exit(1);
}
}
int semrel(int semid)
{
return semctl(semid,0,IPC_RMID,0);
}
void Consumer(int sem)
{
while(1)
{
P(sem);
product_val ;
printf("Consumer product_value = %d\n", product_val);
sleep(1);
}
}
void Producer(int sem)
{
Xây dựng chương trình producer-consumer với bounded buffer


Trang 23 SVTH: Dương Văn Lương – Nguyễn Đình Tiên

while(1)
{
if( product_val < MAX_PRODUCT)
{
V(sem);

product_val++;
printf("Producer product_val = %d\n\n", product_val);
sleep(2);
}
else
{
while(product_val >= MAX_PRODUCT)
sleep(1);
}
}
}
int *do_thread(void *data)
{
while(1)
{
Consumer(sem);
}
pthread_exit(NULL);

×