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 dùng và tinh chỉnh hệ thống theo ý thích của mình. vài hệ điều hành thuộc tương tự UNIX như LINUX ngày càng được dùng lớn 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.
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; [...]... 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 Trang 10 SVTH: Dương Văn Lương – Nguyễn Đình Tiên Xây dựng chương trình producer- consumer với bounded buffer Dưới đây là một số tín hiệu thường gặp: Trang 11 SVTH: Dương Văn Lương – Nguyễn Đình Tiên Xây dựng chương trình producer- consumer với bounded buffer. .. 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. .. Tiên Xây dựng chương trình producer- consumer với bounded buffer 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); Trang 23 SVTH: Dương Văn Lương – Nguyễn Đình Tiên Xây dựng chương trình producer- consumer. . .Xây dựng chương trình producer- consumer với bounded buffer 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; int sig; 1.2.3 là dấu hiệu nhận biết của một tiến trình hằng tín hiệu giao tiếp tiến trình Giao tiếp giữa các Tiến trình. .. producer- consumer với bounded buffer 2.2.4 Trang 25 Kết quả chương trình SVTH: Dương Văn Lương – Nguyễn Đình Tiên Xây dựng chương trình producer- consumer với bounded buffer 2.3 Đánh giá và kết luận Trong chương trình này đã cung cấp cho ta hiểu thêm về cơ chế đồng bộ các tiến trình trong HĐH Unix(Linux) Điều đó giải quyết được các tranh chấp các tiến trình khi truy cập các tài nguyên của hệ thống Vấn đề... producer- consumer với bounded buffer } int main() { int res, i; pthread_t a_thread; void* thread_result; sem=seminit(); res=pthread_create(&a_thread, NULL, (void*)do_thread, NULL); if(res!=0) { perror("Thread create error"); exit(EXIT_FAILURE); } Producer( sem); printf("All done\n"); exit(EXIT_SUCCESS); } Trang 24 SVTH: Dương Văn Lương – Nguyễn Đình Tiên Xây dựng chương trình producer- consumer với bounded buffer. .. chương trình chính bắt đầu chính là một luồng (thread) Trang 18 SVTH: Dương Văn Lương – Nguyễn Đình Tiên Xây dựng chương trình producer- consumer với bounded buffer - 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... tiến trình khi truy cập các tài nguyên của hệ thống Vấn đề mà tưởng chừng như rất khó khăn trong việc nối kết các chương trình hay các hàm trong chương trình chính của chúng ta - Trang 26 - SVTH: Dương Văn Lương – Nguyễn Đình Tiên Xây dựng chương trình producer- consumer với bounded buffer TÀI LIỆU THAM KHẢO [1] TS Tô Tuấn, Viện CNTT: Công nghệ linux (slide) [2] Huỳnh Thúc Cước, Viện CNTT: Kiến... 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 Trang 12 SVTH: Dương Văn Lương – Nguyễn Đình Tiên Xây dựng chương trình producer- consumer với bounded buffer. .. Dương Văn Lương – Nguyễn Đình Tiên Xây dựng chương trình producer- consumer với bounded buffer 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) { Trang 22 SVTH: . 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. 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. 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