ĐẠI HỌC QUỐC GIA HÀ NỘI TRƯỜNG ĐẠI HỌC CÔNG NGHỆ Phạm Thanh Hải NGHIÊN CỨU VỀ ĐẶC TẢ VÀ KIỂM CHỨNG RÀNG BUỘC THỜI GIAN GIỮA CÁC THÀNH PHẦN TRONG CHƯƠNG TRÌNH TƯƠNG TRANH LUẬN VĂN THẠC
Trang 1ĐẠI HỌC QUỐC GIA HÀ NỘI
TRƯỜNG ĐẠI HỌC CÔNG NGHỆ
Phạm Thanh Hải
NGHIÊN CỨU VỀ ĐẶC TẢ VÀ KIỂM CHỨNG RÀNG BUỘC THỜI GIAN GIỮA CÁC THÀNH PHẦN TRONG
CHƯƠNG TRÌNH TƯƠNG TRANH
LUẬN VĂN THẠC SĨ CÔNG NGHỆ THÔNG TIN
Hà Nội - 2015
HÀ NỘI - 2011
Trang 2Lời Cam Đoan
ĐẠI HỌC QUỐC GIA HÀ NỘI
TRƯỜNG ĐẠI HỌC CÔNG NGHỆ
Phạm Thanh Hải
NGHIÊN CỨU VỀ ĐẶC TẢ VÀ KIỂM CHỨNG RÀNG BUỘC THỜI GIAN GIỮA CÁC THÀNH PHẦN TRONG
CHƯƠNG TRÌNH TƯƠNG TRANH
Ngành: Công nghệ thông tin
Chuyên ngành : Kỹ thuật phần mềm
Mã số: 60480103
LUẬN VĂN THẠC SĨ CÔNG NGHỆ THÔNG TIN
NGƯỜI HƯỚNG DẪN KHOA HỌC: PGS.TS Nguyễn Việt Hà
Hà Nội - 2015
Trang 3LỜI CÁM ƠN
Đầu tiên tôi xin gửi lời cảm ơn sâu sắc tới thầy giáo PGS.TS Nguyễn Việt Hà, bộ môn công nghệ phần mềm, khoa công nghệ thông tin, trường Đại học Công nghệ – Đại học Quốc Gia Hà Nội người đã định hướng đề tài và tận tình hướng dẫn chỉ bảo tôi trong suốt quá trình thực hiện luận văn tốt nghiệp này
Tôi cũng xin trân trọng cảm ơn quý thầy cô trong Khoa Công nghệ thông tin trường Đại học Công nghệ – Đại học Quốc Gia Hà Nội đã tận tình giảng dạy, truyền đạt những kiến thức quý báu trong suốt hai năm học làm nền tảng cho tôi thực hiện luận văn tốt nghiệp này
Tôi cũng xin được cảm ơn các tác giả của các công trình nghiên cứu, tài liệu đã được tôi sử dụng, trích dẫn trong luận văn vì đã cung cấp nguồn tư liệu quý báu và các kiến thức liên quan để tôi thực hiện luận văn
Con xin cảm ơn cha mẹ và gia đình đã sinh ra và nuôi dạy con khôn lớn, luôn bên cạnh động viên và ủng hộ con trên con đường mà con yêu thích và lựa chọn
Cám ơn các bạn học viên cao học Khoa công nghệ thông tin khóa K19 Các bạn đã giúp đỡ và ủng hộ tôi rất nhiều cũng như đóng góp nhiều ý kiến quý báu, qua đó giúp tôi hoàn thiện luận văn tốt hơn
Mặc dù đã rất nỗ lực, cố gắng nhưng chắc hẳn luận văn của tôi vẫn còn nhiều thiếu sót Tôi rất mong nhận được nhiều những ý kiến đánh giá quý, phê bình của quý thầy cô, của anh chị và các bạn
Một lần nữa tôi xin chân thành cảm ơn!
Hà Nội, tháng 11 năm 2015
Phạm Thanh Hải
Trang 4LỜI CAM ĐOAN
Tôi xin cam đoan luận văn tốt nghiệp “Nghiên cứu về đặc tả và kiểm chứng ràng buộc thời gian giữa các thành phần trong chương trình tương tranh” là công trình nghiên cứu của riêng tôi dưới sự hướng dẫn của PGS.TS Nguyễn Việt Hà Các số liệu các kết quả được trình bày trong luận văn là hoàn toàn trung thực và chưa từng được công bố trong bất kỳ một công trình nào khác
Tôi đã trích dẫn đầy đủ các tài liệu tham khảo, công trình nghiên cứu liên quan ở trong nước và quốc tế Ngoại trừ các tài liệu tham khảo này, luận văn hoàn toàn là công việc của riêng tôi
Nếu có phát hiện nào về sự gian lận sao chép tài liệu, công trình nghiên cứu của tác giả khác mà không được ghi rõ trong phần tài liệu tham khảo, tôi sẽ chịu hoàn toàn trách nhiệm về kết quả luận văn của mình
Trang 5MỤC LỤC
LỜI CÁM ƠN i
LỜI CAM ĐOAN ii
MỤC LỤC iii
DANH MỤC CÁC KÝ HIỆU VÀ CHỮ VIẾT TẮT v
DANH SÁCH BẢNG vi
DANH SÁCH HÌNH VẼ vii
Chương 1 Giới thiệu 1
1.1 Bối cảnh 1
1.2 Một số nghiên cứu liên quan 2
1.3 Nội dung nghiên cứu 3
1.4 Cấu trúc luận văn 4
Chương 2 Kiến thức cơ sở 5
2.1 Kiểm chứng phần mềm 5
2.1.1 Kiểm chứng hình thức 5
2.1.1.1 Kiểm chứng mô hình 5
2.1.1.2 Chứng minh định lý 5
2.1.2 Kiểm chứng tại thời điểm thực thi 6
2.2 Một số vấn đề trong chương trình tương tranh 6
2.3 Sự tương tranh trong Java 7
2.4 Lập trình hướng khía cạnh 8
2.4.1 Thực thi cắt ngang 11
2.4.2 Điểm nối 12
2.4.3 Hướng cắt 12
2.4.4 Mã hành vi 12
2.4.5 Khía cạnh 14
2.4.6 Cơ chế hoạt động của AspectJ 15
2.5 Kết luận 16 Chương 3 Ràng buộc thời gian giữa các thành phần trong chương trình tương tranh 17
Trang 63.1 Giới thiệu 17
3.2 Bài toán kiểm chứng ràng buộc thời gian giữa các thành phần tương tranh 17
3.3 Phương pháp đặc tả và kiểm chứng ràng buộc thời gian 19
3.3.1 Mô tả phương pháp 19
3.3.2 Đặc tả ràng buộc thời gian 19
3.3.3 Biểu thức chính quy thời gian 21
3.3.4 Phương pháp sinh mã aspect 21
3.3.4.1 Đọc và phân tích biểu thức chính quy thời gian 21
3.3.4.2 Sinh mã aspect 22
3.4 Kết luận 22
Chương 4 Thực nghiệm 26
4.1 Xây dựng công cụ TVG 26
4.1.1 Đọc và phân tích biểu thức chính quy thời gian 27
4.1.2 Sinh mã aspect 30
4.2 Kiểm chứng một số chương trình 33
4.2.1 Kiểm chứng chương trình tuần tự 33
4.2.2 Kiểm chứng chương trình tương tranh 36
Chương 5 Kết luận 40
Phụ lục 41
Phụ lục A Công cụ sinh mã kiểm chứng TVG 41
A.1 Giới thiệu 41
A.2 Hướng dẫn sử dụng 41
A.2.1 Các yêu cầu cài đặt 41
A.2.2 Các chức năng chính 42
A.2.3 Hướng dẫn thực hiện 42
TÀI LIỆU THAM KHẢO 44
Tiếng Việt 44
Tiếng Anh 44
Trang 7DANH MỤC CÁC KÝ HIỆU VÀ CHỮ VIẾT TẮT
AOP Aspect Oriented Programming Lập trình hướng khía cạnh CFG Context Free Grammar Văn phạm phi ngữ cảnh
IDE Integrated Development Môi trường pháp triển tích hợp
Environment MCS Method Call Sequence Tuần tự gọi phương thức OOP Object Oriented Programming Lập trình hướng đối tượng
TVG Timed Verify Generator Công cụ kiểm sinh mã
TC Timing constraint Ràng buộc thời gian
TRE Timed Regular Expressions Biểu thức chính quy thời gian
Trang 8DANH SÁCH BẢNG
Bảng 2.1 - Ánh xạ giữa các loại điểm nối (joinpoint) và hướng cắt (pointcut) tương
ứng 14
Trang 9DANH SÁCH HÌNH VẼ
Hình 1.1 – Kiểm chứng chương trình mức cài đặt sử dụng lập trình AOP 4
Hình 2.1 – Ví dụ sử dụng phương thức run 7
Hình 2.2 – Xung đột các tiến trình trong Java 8
Hình 2.3 – Sử dụng synchoronized để giải quyết tương tranh 8
Hình 2.4 – Xử lý cắt ngang trong lập trình OOP 10
Hình 2.5 – Xử lý cắt ngang trong lập trình AOP 10
Hình 2.6 – Biểu diễn một khía cạnh với mã hành vi trước và sau 13
Hình 2.7 – Cấu trúc cơ bản của một khía cạnh 14
Hình 3.1 – Mã nguồn một chương trình bao gồm thành phần tuần tự và tương tranh 17
Hình 3.2 – Mô tả quá trình chạy các phương thức 18
Hình 3.3 – Phương pháp kiểm chứng ràng buộc thời gian 20
Hình 3.4 – Thuật toán đọc biểu thức chính quy thời gian 23
Hình 3.5 – Ví dụ một mẫu aspect 24
Hình 3.6 – Thuật toán Sinh mã aspect 25
Hình 4.1 – Cài đặt công cụ TVG bằng Netbean 7.0.1 26
Hình 4.2 – Một đoạn mã aspect sinh ra từ công cụ TVG 27
Hình 4.3 – Pattern kiểm tra từng thành phần trong TRE 28
Hình 4.4 – Lớp TimingMethod 28
Hình 4.5 – Quá trình đọc và phân tích biểu thức TRE 30
Hình 4.6 – Tạo mã aspect từ các mẫu đã định nghĩa 31
Hình 4.7 – Template aspectHead 32
Hình 4.8 – Template aspectTail 32
Hình 4.9 – Mã nguồn một chương trình tuần tự 34
Hình 4.10 – Kết quả thực nghiệm ca kiểm thử đúng chương trình tuần tự 35
Hình 4.11 – Kết quả thực nghiệm ca kiểm thử sai chương trình tuần tự 35
Hình 4.12 – Mã nguồn một chương trình tương tranh 38
Hình 4.13 – Kết quả thực nghiệm ca kiểm thử đúng chương trình tương tranh 39
Hình 4.14 – Kết quả thực nghiệm ca kiểm thử sai chương trình tương tranh 39
Trang 10Hình A.1 – Giao diện chính của công cụ sinh mã TVG 41 Hình A.2 – Đặc tả ràng buộc thời gian các thành phần bằng TRE 42 Hình A.3 – Đan mã aspect với mã chương trình Java viết bằng Eclipse 43
Trang 11Chương 1 Giới thiệu
1.1 Bối cảnh
Phần mềm hiện đóng một vai trò vô cùng quan trọng trong xã hội [24] Quá trình phát
triển phần mềm gồm rất nhiều giai đoạn: Thu thập yêu cầu, phân tích, thiết kế, xây dựng, kiểm thử, triển khai và bảo trì phần mềm Trong các giai đoạn trên thì giai đoạn kiểm thử, phát hiện, xác định và sửa lỗi phần mềm là rất quan trọng để đảm bảo chất lượng của phần mềm Lỗi hệ thống gây ra hậu quả đặc biệt nghiêm trọng không chỉ gây thiệt hại về kinh tế mà còn tổn hại trực tiếp đến sinh mạng con người Lỗi phần mềm được phát hiện càng muộn thì càng gây hậu quả nghiêm trọng, tốn rất nhiều thời gian và công sức để sửa lỗi, thậm chí phải xây dựng lại toàn bộ hệ thống
Vì vậy, trong công nghệ phần mềm đã có nhiều phương pháp khác nhau được đề xuất
và phát triển để giảm lỗi phần mềm từ pha thiết kế đến cài đặt Các phương pháp kiểm
chứng như chứng minh định lý (theorem proving) và kiểm chứng mô hình (model checking) đã ứng dụng thành công để kiểm chứng mô hình thiết kế của phần mềm [7,
5] Trong nhiều hệ thống, cài đặt thực tế thường chỉ được thực hiện sau khi mô hình thiết kế đã được kiểm chứng Tuy nhiên việc cài đặt mã nguồn chương trình hoàn toàn
có thể vi phạm các ràng buộc thiết kế [34] Do đó, phần mềm vẫn tồn tại lỗi mặc dù thiết kế của nó đã được kiểm chứng và thẩm định một cách chi tiết
Các phương pháp kiểm chứng tại thời điểm thực thi [25, 13, 17] như kiểm thử phần
mềm bằng các bộ dữ liệu kiểm thử (test suite) gặp phải một hạn chế là thường chỉ phát hiện được các lỗi về giá trị đầu ra (out put) mà không phát hiện được các lỗi vi phạm
ràng buộc thiết kế
Các phần mềm (chương trình) tương tranh gồm hai hoặc nhiều tiến trình cộng tác với
nhau để thực hiện cùng một nhiệm vụ [19] Trong đó mỗi tiến trình là một chương trình tuần tự thực hiện một tập các câu lệnh tuần tự Các tiến trình thường cộng tác với
nhau thông qua các biến chia sẻ (shared variables) hoặc cơ chế truyền thông điệp (message passing) Lập trình và kiểm chứng chương trình tương tranh thường khó hơn
so với chương trình tuần tự do khả năng thực hiện của các chương trình này Với một chương trình tương tranh chúng ta thực thi hai lần cùng với một đầu vào như nhau thì không thể đảm bảo chương trình sẽ trả về cùng một kết quả
Các nghiên cứu gần đây tập trung vào các vấn đề về xung đột (interference), tắc nghẽn (dead lock) trong chương trình Java tương tranh Tuy nhiên các phương pháp này chưa
kiểm chứng vấn đề về thỏa mãn ràng buộc thời gian như thời gian thực thi của các tiến trình Đối với các hệ thống thời gian thực, hệ thống an toàn – bảo mật việc vi phạm ràng buộc thời gian sẽ gây ra các lỗi hệ thống rất nghiêm trọng Do đó nhu cầu nghiên
Trang 12cứu phương pháp kiểm chứng ràng buộc về thời gian trong chương trình tương tranh trở nên cần thiết
1.2 Một số nghiên cứu liên quan
Đã có một vài phương pháp, công cụ được đề xuất để kiểm chứng ràng buộc thời gian trong các hệ thống phần mềm
SACRES [4] là một môi trường kiểm chứng cho các hệ thống nhúng, cho phép người
sử dụng đặc tả ràng buộc thời gian bằng các biểu đồ thời gian dạng ký hiệu (symbolic timing diagrams) Các đặc tả thiết kế được dịch sang máy hữu hạn trạng thái (finite state machine) được tối ưu và kiểm chứng bằng mô hình ký hiệu (symbolic model checking) Tuy nhiên phương pháp này chỉ kiểm chứng ở mức mô hình, không phải
mức cài đặt
Wegener [33] đề xuất phương pháp để kiểm chứng ràng buộc thời gian trong các hệ
thống thời gian thực dựa trên kỹ thuật kiểm chứng tiến hóa (evolutionary testing) Trong đó, vi phạm ràng buộc thời gian được định nghĩa là đầu ra (output) được đưa ra
quá nhanh hoặc quá chậm so với đặc tả Do đó, nhiệm vụ của người kiểm thử là thiết
kế các đầu vào (input) với thời gian thực hiện nhanh nhất hoặc chậm nhất để phát hiện
các vi phạm Việc thiết kế đầu vào được quy về bài toán tối ưu trong tính toán tiến hóa
để tự động tìm đầu vào với thời gian thực hiện nhanh nhất và chậm nhất Tuy nhiên phương pháp này chưa kiểm chứng được ràng buộc thời gian giữa các thành phần Guo và Lee [20] đề xuất phương pháp kết hợp giữa đặc tả và kiểm chứng ràng buộc thời gian cho các hệ thống thời gian thực Trong đó, ràng buộc thời gian cùng với yêu cầu hệ thống được đặc tả bằng môđun TER nets [30] Giống như [4] phương pháp này chỉ kiểm chứng ở mức mô hình, không phải ở mức cài đặt
Trong [10] phương pháp sử dụng biểu đồ thời gian của UML được đề xuất để ước lượng thời gian thực thi trong trường hợp xấu nhất của các thành phần hệ thống ở thời điểm thiết kế Thời gian thực thi được ước lượng dựa trên biểu đồ ca sử dụng kết hợp với các thông tin về hành vi của người sử dụng hệ thống trong tương lai Phương pháp này cũng không kiểm chứng ràng buộc thời gian thực thi giữa các thành phần
Jin [35] đề xuất phương pháp hình thức để kiểm chứng tĩnh thứ tự thực hiện của các
phương thức (Method Call Sequence – MCS) trong chương trình Java tuần tự phương
pháp này sử dụng ôtômát hữu hạn trạng thái để đặc tả giao thức, các chương trình Java
được biến đổi thành các văn phạm phi ngữ cảnh (context free grammar – CFG) sử
dụng công cụ Accent Ngôn ngữ sinh ra bởi ôtômát L(A) được so sánh với ngôn ngữ sinh ra bởi CFG L(G), Nếu 𝐿 𝐺 ⊆ 𝐿(𝐴) thì chương trình Java tuân theo đặc tả giao thức, và ngược lại Ưu điểm của phương pháp này đó là các vi phạm có thể được phát
Trang 13hiện sớm, tại thời điểm phát triển hoặc biên dịch chương trình Do đó sự thực thi của chương trình không bị ảnh hưởng
Deline và Fahndrich [30] đề xuất phương pháp kiểm chứng vào thời điểm thực thi sự tuân thủ cài đặt và đặc tả MCS Phương pháp này sử dụng máy trạng thái để đặc tả MCS Đặc tả MCS sau đó được biên dịch sang mã nguồn và đan xen với mã nguồn chương trình để kiểm chứng động sự tuân thủ của cài đặt so với đặc tả MCS Các mệnh đề tiền và hậu điều kiện của các phương thức trong MCS cũng được đặc tả và kiểm chứng Tuy nhiên, phương pháp này chưa kiểm chứng được ràng buộc thời gian giữa các thành phần
Yoonsik và Perumandla [8, 9] mở rộng ngôn ngữ đặc tả, và trình biên dịch JML để biểu diễn giao thức tương tác bằng biểu thức chính quy Sau đó, biểu thức chính quy được biên dịch thành mã thực thi để chạy đan xen với chương trình gốc để kiểm chứng
sự tuân thủ giữa cài đặt so với đặc tả giao thức tương tác Các hành vi của chương trình gốc sẽ không bị thay đổi ngoại trừ thời gian và kích thước Như [35] phương pháp này chưa kiểm chứng được ràng buộc thời gian giữa các thành phần so với đặc
tả
1.3 Nội dung nghiên cứu
Trong luận văn này, tác giả tập trung nghiên cứu phương pháp để kiểm chứng ràng buộc thời gian chương trình tương tranh ở pha cài đặt mã nguồn chương trình Sử dụng
phương pháp lập trình hướng khía cạnh (AOP) để kiểm chứng ràng buộc thời gian giữa
các thành phần trong chương trình tương tranh (Hình 1.1) cụ thể là thời gian thực thi của các phương thức trong chương trình Các vi phạm sẽ được phát hiện tại thời điểm thực thi chương trình
Trong hướng tiếp cận này, ràng buộc về thời gian giữa các phương thức của một ứng
dụng hướng đối tượng (OOP) viết bằng ngôn ngữ Java sẽ được đặc tả bằng biểu thức chính quy thời gian Từ các đặc tả đầu vào này mã khía cạnh (aspect) sẽ được tự động
sinh ra đan với mã của các thành phần chương trình từ đó kiểm chứng sự tuân thủ ràng buộc thời gian so với đặc tả Khi đó, trong quá trình chạy của chương trình, các đoạn
mã aspect sẽ tự động kiểm tra thời gian thực thi các thành phần trong chương trình và thông báo lỗi khi có vi phạm xảy ra
Tác giả tập trung vào việc xây dựng công cụ TVG (Timed Verify Generator) để sinh
mã aspect kiểm chứng một cách tự động từ đặc tả ràng buộc thời gian Đầu vào của công cụ TVG là các biểu thức chính quy thời gian Và đầu ra là các đoạn mã aspect kiểm chứng
Trang 141.4 Cấu trúc luận văn
Nội dung luận văn được trình bày trong năm chương Chương 1 giới thiệu về đề tài nghiên cứu Chương này trình bày ngữ cảnh, các nghiên cứu liên quan, lý do lựa chọn
đề tài, nội dung nghiên cứu của đề tài và cấu trúc nội dung của luận văn Chương 2 trình bày các kiến thức nền phục vụ cho đề tài Chương này mô tả các kiến thức cơ bản
về kiểm chứng phần mềm, một số vấn đề trong chương trình tương tranh, sự tương tranh trong Java và phương pháp lập trình hướng khía cạnh Chương 3 trình bày nội dung chính nghiên cứu của luận văn là phương pháp đặc tả và kiểm chứng các ràng buộc thời gian giữa các thành phần tuần tự và song song trong chương trình tương tranh Chương 4 giới thiệu về công cụ và kết quả thực nghiệm của phương pháp Chương 5 đưa ra kết luận, định hướng phát triển cho đề tài Cuối cùng là tài liệu tham khảo được sử dụng trong luận văn
Đặc tả yêu cầu
Kiểm chứng thiết kế
Cài đặt chương trình
Kiếm chứng chương trình Lập trình hướng khía cạnh
Bản thiết kế
Đúng
Đúng Sai
Sai
Đúng
Hình 1.1 – Kiểm chứng chương trình mức cài đặt sử dụng lập trình AOP
Trang 15Chương 2 Kiến thức cơ sở
2.1 Kiểm chứng phần mềm
Kiểm chứng phần mềm (software verification) là tập các nguyên lý, phương pháp và
công cụ để đảm bảo tính đúng đắn của các sản phẩm phần mềm Trong mục này, luận văn sẽ giới thiệu khái quát hai phương pháp kiểm chứng phần mềm là kiểm chứng hình thức và kiểm chứng tại thời điểm thực thi chương trình
2.1.1 Kiểm chứng hình thức
2.1.1.1 Kiểm chứng mô hình
Phương pháp kiểm chứng mô hình (model checking) được sử dụng để xác định tính
hợp lệ của một hay nhiều tính chất mà người dùng quan tâm trong một mô hình phần mềm cho trước Cho mô hình M và thuộc tính p cho trước, nó kiểm tra thuộc tính p có thỏa mãn trong mô hình M hay không, ký hiệu 𝑀 ⊨ 𝑝 [5] Về mặt thực thi kiểm chứng
mô hình sẽ duyệt qua các trạng thái, các đường thực thi có thể có trong mô hình 𝑀 để xác định tính khả thỏa của p Trong đó các thuộc tính được đặc tả bằng logic thời gian LTL hoặc CTL [5] Mô hình M là một cấu trúc Kripke gồm bốn thành phần M = (S, S0,
L, R) với 𝑆 là một tập hữu hạn các trạng thái, S0 ∈ S là trạng thái đầu, 𝑅 ⊂ 𝑆 × 𝑆 là quan hệ chuyển trạng thái, 𝐿 ∶ 𝑆 → 2𝐴𝑃 là hàm gán nhãn với AP là tập hữu hạn các mệnh đề nguyên tử được xây dựng từ hệ thống
Một bộ kiểm chứng mô hình [21, 29] (model checker) sẽ kiểm tra tất cả các trạng thái
có thể có của mô hình để tìm ra tất cả các đường thực thi có thể gây ra lỗi Do đó không gian trạng thái thường là vô cùng lớn nếu không muốn nói là vô hạn Vì vậy việc duyệt qua tất cả các trạng thái là bất khả thi Để đối phó với bài toán bùng nổ không gian trạng thái đã có một vài các nghiên cứu liên quan đến các kỹ thuật làm giảm không gian trạng thái như Abtraction, Slicing [15, 34]
2.1.1.2 Chứng minh định lý
Phương pháp chứng minh định lý (theorem proving) [32] sử dụng các kỹ thuật suy
luận để chứng minh tính đúng đắn của một công thức hay tính khả thỏa một công thức
F với tất cả các mô hình, ký hiệu ⊨ 𝐹 Trong đó, Pivới 𝑖 = 1 … 𝑛 là tập các tiên đề, C
là tập các định lý Một hệ thống gọi là đúng (sound) nếu tất cả các định lý của nó được chứng minh
Các phương pháp chứng minh định lý như B [27], Event-B [26] đã được sử dụng
thành công để đặc tả và kiểm chứng tính đúng đắn của mô hình thiết kế phần mềm
Trang 16P1, P2,…,Pn
├ C
name
2.1.2 Kiểm chứng tại thời điểm thực thi
Kiểm chứng tại thời điểm thực thi (runtime verification) [18] là kỹ thuật kết hợp giữa
kiểm chứng hình thức và thực thi chương trình để phát hiện các lỗi của hệ thống dựa trên quá trình quan sát input/output khi thực thi chương trình Các hành vi quan sát được sẽ được theo dõi và kiểm tra có thỏa mãn với đặc tả yêu cầu của hệ thống So sánh với phương pháp kiểm chứng tĩnh thì kiểm chứng tại thời điểm thực thi được thực hiện trong khi thực thi hệ thống Vì vậy phương pháp này còn được gọi là kiểm
thử bị động (passive testing) Kiểm chứng tại thời điểm thực thi nhằm đảm bảo sự tuân
thủ giữa cài đặt hệ thống phần mềm so với đặc tả thiết kế của nó Các trường hợp thường lựa chọn kiểm chứng tại thời điểm thực thi
- Không thể đảm bảo được tính đúng đắn giữa sự cài đặt chương trình so với đặc
tả thiết kế của nó
- Nhiều thông tin chỉ có sẵn hoặc thu thập được tại thời điểm thực thi chương trình
- Các hành vi của hệ thống phụ thuộc vào môi trường khi nó được thực thi
- Với các hệ thống an toàn và bảo mật cao
2.2 Một số vấn đề trong chương trình tương tranh
Trong các chương trình tương tranh, có hai thuộc tính cơ bản cần đảm bảo là an toàn
(safety) và thực hiện được (liveness) [6, 31] Thuộc tính an toàn đảm bảo chương trình luôn thỏa mãn (luôn đúng) các ràng buộc của nó Ví dụ như ràng buộc sự xung đột (interference) giữa các tiến trình Thuộc tính thực hiện được đảm bảo chương trình cuối cùng sẽ thỏa mãn (sẽ đúng) các ràng buộc của nó
Sự xung đột (interference) xảy ra khi hai hoặc nhiều tiến trình đồng thời truy cập một
biến chia sẻ, trong đó ít nhất một tiến trình ghi và các tiến trình khác không có cơ chế
để ngăn chặn sự truy cập đồng thời này Khi đó giá trị của biến chia sẻ và kết quả của
chương trình sẽ phụ thuộc vào sự đan xen (interleaving) hay thứ tự thực hiện của các tiến trình Sự xung đột còn được gọi là cạnh tranh dữ liệu (datarace)
Tắc nghẽn xảy ra khi hệ thống (chương trình) không thể đáp ứng được bất kỳ tín hiệu
hoặc yêu cầu nào Có hai dạng tắc nghẽn, dạng một xảy ra khi các tiến trình dừng lại
và chờ đợi lẫn nhau, ví dụ một chương trình nắm giữ khóa mà các tiến trình khác mong muốn và ngược lại Dạng hai xảy ra khi một tiến trình chờ đợi một tiến trình
Trang 17khác không kết thúc Một thuộc tính khác tương tự như khóa chết khi các tiến trình liên tục thay đổi trạng thái của nó để đáp ứng với những thay đổi của những tiến trình
khác mà không đạt được mục đích cuối cùng Sự đói (starvation) liên quan đến tranh
chấp tài nguyên, vấn đề này xảy ra khi một tiến trình không thể truy cập tài nguyên chia sẻ
2.3 Sự tương tranh trong Java
Java là ngôn ngữ lập trình hướng đối tượng hỗ trợ lập trình tương tranh với cơ chế đồng bộ hóa giữa các tiến trình, mô phỏng bộ nhớ chia sẻ, môi trường lập trình trực quan và hệ thống thư viện phong phú với nhiều giao diện lập trình tương tranh khác nhau Java được biết đến như một ngôn ngữ lập trình tương tranh được sử dụng rộng rãi trong công nghiệp phần mềm Đặc biệt từ phiên bản 5.0, Java hỗ trợ thư viện APIs
bậc cao java.util.concurrent khiến việc cài đặt chương trình tương tranh trở nên khá dễ
dàng
Sự tương tranh trong Java [28] được thực hiện thông qua cơ chế giám sát các tiến
trình, hành vi của tiến trình được mô tả trong phương thức run Một ví dụ sử dụng
phương thức run được trình bày trong Hình 2.1
Sự thực thi của một tiến trình có thể được điều khiển bởi một tiến trình khác thông qua
các phương thức stop, suspend và resum Tuy nhiên, với các hệ thống lớn gồm nhiều
tiến trình thì việc sử dụng các phương thức này để điều khiển sự thực thi của các tiến trình là không an toàn, do chúng ta khó có thể kiểm soát tất cả các tiến trình Một ví dụ cho vấn đề này được trình bày trong Hình 2.2 Kết quả trả về của phương thức trong ví
dụ Hình 2.2 là 9965 do có sự xung đột giữa các tiến trình
Do đó, Java cung cấp một mô hình tương tranh để giải quyết sự đồng bộ hóa giữa các tiến trình Khi nhiều tiến trình cùng muốn truy cập vào dữ liệu chia sẻ trong một vùng
giới hạn được đánh dấu bằng từ khóa synchoronized thì tại một thời điểm khóa của
vùng xung đột chỉ cho phép một tiến trình được phép truy cập Một ví dụ sử dụng
synchoronized để giải quyết tương tranh được trình bày trong Hình 2.3 Thay vì gọi
Trang 18phương thức increment() chúng ta gọi phương thức đã được thêm từ khóa
synchoronized cụ thể là phương thức incrementSync() Kết quả trả về lúc này sẽ là
10000 do quá trình xung đột giữa các tiến trình đã được giải quyết
xung đột mà bị chiếm giữ bởi tiến trình khác Các tiến trình có thể được đánh thức
bằng các phương thức notify hoặc notifyAll Chiến lược giám sát ở mức thấp của Java
không tránh được các lỗi liên quan về tương tranh như khóa chết, xung đột
Chúng ta sẽ lấy một ví dụ để thấy được sự khác nhau giữa AOP và OOP Ở đây tác giả
sẽ sử dụng lớp Point với cấu trúc được định nghĩa trong Hình 2.4
Khi chúng ta di chuyển Point từ một vị trí này sang vị trí khác tức là phải đặt lại giá trị của x và y, ta phải cập nhật lại thông qua phương thức display.update(this) Như vậy, cùng một phương thức display.update(this) ta phải viết lại ở ba vị trí khác
Trang 19nhau ứng với ba sự thay đổi Hãy tưởng tượng đối với mã nguồn chương trình đủ lớn
và có hàng nghìn lần thay đổi tương tự như thay đổi ở trên dòng mã
display.update(this) sẽ phải xuất hiện ở hàng nghìn chỗ khác nhau Chưa kể tới vấn
đề bảo trì và nâng cấp, một ví dụ là khách hàng yêu cầu ngoài việc hiển thị thông tin chúng ta cần lưu ra một file log.txt Lúc này lập trình viên phải dò lại tất cả mã nguồn và chèn thêm vào đó dòng lệnh lưu vết ra file log.txt Lập trình hướng đối tượng OOP có thể giải quyết rất tốt các chức năng chính của hệ thống nhưng nó lại gặp rất nhiều khó khăn trong việc giải quyết các chứng năng cắt ngang hệ thống Khi sử dụng OOP để thực hiện các chức năng cắt ngang hệ thống sẽ gặp phải hai vấn đề chính
là: Chồng chéo mã nguồn (Code tangling) và dàn trải mã nguồn (Code scattering)
- Chồng chéo mã nguồn: Mô-đun chính của hệ thống ngoài việc thực hiện các yêu cầu chính, nó còn phải thực hiện các yêu cầu khác như: tính đồng bộ, bảo mật, lưu vết, lưu trữ Như vậy, trong một mô-đun có rất nhiều loại mã khác nhau Điều này làm cho tính mô-đun hóa của hệ thống giảm đi, khả năng sử dụng lại mã nguồn thấp, khó bảo trì hệ thống
- Dàn trải mã nguồn: Cùng một mã nguồn của các chức năng cắt ngang hệ thống được cài đặt lặp đi lặp lại rất nhiều lần ở nhiều mô-đun chính của hệ thống Điển hình như ví dụ trong Hình 2.4
Nền tảng cơ bản của AOP khác với OOP là cách quản lý các chức năng cắt ngang hệ thống Việc thực thi của từng chức năng cắt ngang AOP bỏ qua các hành vi được tích hợp vào nó Với AOP, chúng ta có thể cài đặt các mối quan tâm chung cắt ngang hệ
thống bằng các mô-đun đặc biệt gọi là khía cạnh (aspect) thay vì dàn trải chúng trên
các mô-đun nghiệp vụ liên quan Nhờ mã được tách riêng biệt, các vấn đề đan xen trở nên dễ kiểm soát hơn Các khía cạnh của hệ thống có thể thay đổi, thêm hoặc xóa lúc biên dịch và có thể tái sử dụng Một trình biên dịch đặc biệt là Aspect Weaver thực hiện các thành phần riêng lẻ thành một hệ thống thống nhất
Quay trở lại với ví dụ trong Hình 2.4 Nếu sử dụng lập trình hướng khía cạnh AOP, các chức năng cắt ngang hệ thống: Cập nhật hình, lưu vết sẽ được giải quyết theo cách sau:
Thay vì tích hợp trực tiếp trong mô-đun nghiệp vụ chính, lập trình viên sẽ tách chúng thành các mô-đun mới gọi là aspect Hình 2.5 mô tả mã nguồn cho việc thực thi chức năng cập nhật bằng cách sử dụng AOP
Sau khi đã định nghĩa một aspect như vậy thì bất cứ khi nào có sự thay đổi về hình (setX, setY, moveBy) chương trình sẽ tự động gọi chức năng cập nhật cụ thể ở đây là phương thức display.update(this) Đơn giản chúng ta chỉ cần cài đặt các aspect mà không cần phải thay đổi mã nguồn của chương trình chính
Trang 20package Test;
public class Point {
private int x = 0, y = 0;
int getX(){return x;}
int gety(){return y;}
void moveBy(int dx, int dy)
public aspect TestAspect {
pointcut updateDisplay(): execution(void *.setX(int))
- Mô-đun hóa những vấn đề đan xen nhau: AOP xác định vấn đề một cách riêng biệt, cho phép mô-đun hóa những vấn đề liên quan đến nhiều đối tƣợng
Trang 21- Tái sử dụng mã nguồn tốt hơn: Các aspect là những mô-đun riêng biệt, được kết hợp linh động đây chính là yếu tố quan trọng để tái sử dụng mã nguồn
- Cho phép mở rộng hệ thống dễ dàng hơn: Một thiết kế tốt phải tính tới cả những yêu cầu trong hiện tại và tương lai, việc xác định các yêu cầu trong tương lai là một công việc khó khăn Nhưng nếu bỏ qua các yêu cầu trong tương lai, có thể bạn phải thay đổi hay thực hiện lại nhiều thành phần hệ thống Với AOP, người thiết kế có thể để lại quyết định thiết kế cho những yêu cầu phát sinh trong tương lai nhờ thực hiện các aspect riêng biệt
AspectJ [2, 16, 22] là một công cụ AOP cho ngôn ngữ lập trình Java AspectJ gồm hai thành phần cơ bản là:
- Đặc tả ngôn ngữ: Chỉ ra cách viết mã, với AspectJ các chức năng cơ bản của hệ thống sẽ được viết bằng Java còn các chức năng cắt ngang hệ thống sẽ được thực hiện bởi AspectJ
- Phần thực thi: Cung cấp các công cụ biên dịch, gỡ rối Trình biên dịch AspectJ
sẽ đan xen chương trình Java chính với các khía cạnh thành các tệp mã bytecode chạy trên chính máy ảo java AspectJ được plugin vào công cụ Eclipse
và được đánh giá là một trong những sản phẩm tốt nhất về AOP
Trong mục này luận văn sẽ trình bày một số khái niệm liên quan về AspectJ
2.4.1 Thực thi cắt ngang
Trong AspectJ, quá trình biên dịch thực thi các quy tắc đan để kết hợp các mô-đun cắt
ngang với các mô-đun nghiệp vụ chính được gọi là thực thi cắt ngang (crosscutting)
AspectJ định nghĩa hai loại thực thi cắt ngang là thực thi cắt ngang động và thực thi
cắt ngang tĩnh: Thực thi cắt ngang động (dynamic crosscutting) là việc đan các hành vi
mới vào quá trình thực thi một chương trình Trình biên dịch sẽ dựa vào tập các quy tắc đan để xác định điểm đan và chèn thêm hoặc thay thế luồng thực thi chương trình chính bằng mô-đun cắt ngang, từ đó làm thay đổi hành vi của hệ thống Hầu hết việc thực thi cắt ngang trong AspectJ đều là thực thi cắt ngang động
Thực thi cắt ngang tĩnh (static crosscutting) là quá trình đan một sửa đổi vào cấu trúc
tĩnh của lớp, giao diện hay các khía cạnh hệ thống Chức năng chính của thực thi cắt ngang tĩnh là hỗ trợ cho thực thi cắt ngang động Ví dụ thêm dữ liệu và phương thức mới vào lớp đã có nhằm định nghĩa trạng thái và hành vi của lớp để sử dụng trong các hành vi cắt ngang động Thực thi cắt ngang tĩnh còn được sử dụng nhằm khai báo các cảnh báo và lỗi tại thời điểm biên dịch cho nhiều mô-đun
Trong AOP, mỗi khía cạnh được coi là một biểu diễn của một quy tắc đan, nó chỉ ra mô-đun cần đan, vị trí đan trong mô-đun đó, hành vi sẽ được đan vào thông qua các định nghĩa của điểm nối, hướng cắt và mã hành vi
Trang 222.4.2 Điểm nối
Điểm nối (joinpoint) là bất kỳ điểm nào có thể xác định được trong khi thực thi
chương trình Ví dụ: lời gọi hàm, khởi tạo đối tượng Điểm nối chính là vị trí các hành động thực thi cắt ngang được đan vào
Một số điểm nối trong AspectJ:
- Điểm nối tại hàm khởi tạo
- Điểm nối tại các phương thức
- Điểm nối tại các điểm truy cập thuộc tính
- Điểm nối tại các điểm điều khiển ngoại lệ: Được điều khiển trong khối điều khiển ngoại lệ
2.4.3 Hướng cắt
Trong AspectJ, điểm nối thường được sử dụng trong một hướng cắt (pointcut), mỗi
hướng cắt chứa một nhóm các điểm nối với cùng ngữ cảnh của nó Ta có thể khai báo hướng cắt trong một khía cạnh, một lớp hoặc một giao diện Giống như phương thức,
có thể sử dụng định danh truy cập (public, private) để giới hạn truy cập đến hướng cắt
Các hướng cắt có thể có tên hoặc không tên Các hướng cắt không tên, giống như các lớp không tên, định nghĩa tại nơi sử dụng Các hướng cắt được đặt tên thì có thể tham chiếu từ nhiều nơi khác Cú pháp khai báo hướng cắt
[access specifier] pointcut pointcut-name([args]) : pointcut-definition;
Trong đó access specifier khai báo định danh truy cập có thể là public hoặc private, pointcut-name khai báo tên hướng cắt, [args] khai báo danh sách các tham số, pointcut-definition là định nghĩa hướng cắt
Ví dụ:
private pointcut pc_TestSequenceSecond(): execution(void
TestSequenceSecond());
Sự ánh xạ giữa các loại điểm nối (joinpoint) và hướng cắt (pointcut) được định nghĩa
chi tiết trong Bảng 2.1
2.4.4 Mã hành vi
Mã hành vi (advice) là đoạn mã định nghĩa hành vi được đan vào mã nguồn tại điểm nối [16] Mã hành vi được thực thi trước (before advise), sau (after advise), hoặc xung quanh (around advise) một điểm nối Mã hành vi có thể được sử dụng để đưa ra thông
Trang 23báo trước khi đoạn mã tại điểm nối được thực thi lưu vết hoặc kiểm tra lỗi Trong AspectJ định nghĩa ba loại hành vi như sau:
i Mã hành vi trước là loại được thực thi trước các điểm nối
ii Mã hành vi sau là loại được thực thi ngay sau các điểm nối
iii Mã hành vi xung quanh là loại mạnh nhất, nó bao gồm cả mã hành vi trước và
mã hành vi sau Mã hành vi xung quanh có thể chỉnh sửa đoạn mã tại điểm nối,
nó có thể thay thế, thậm chí bỏ qua sự thực thi tại điểm nối đó Mã hành vi xung quanh phải khai báo giá trị trả về cùng kiểu với kiểu trả về của điểm nối Trong
mã hành vi xung quanh, sự thực thi của điểm nối được thể hiện thông qua proceed() Nếu trong mã hành vi không gọi proceed() thì sự thực thi của điểm nối sẽ bị bỏ qua
Ánh xạ giữa các loại điểm nối (joinpoint) và hướng cắt (pointcut) được mô tả chi tiết
trong Bảng 2.1
Hướng cắt xác định điểm cắt cần thiết còn mã hành vi cung cấp các hành động sẽ xảy
ra tại điểm nối đó Hình 2.6 biểu diễn ví dụ về một khía cạnh (aspect) với mã hành vi trước (before) và sau (after)
public aspect test {
pointcut pc_TestSequenceSecond(): execution
Trang 24Bảng 2.1 - Ánh xạ giữa các loại điểm nối (joinpoint) và hướng cắt (pointcut) tương
ứng
Thực hiện phương thức execution(MethodSignature)
Gọi phương thức call(MethodSignature)
Thực hiện hàm dựng execution(ConstructorSignature)
Gọi hàm dựng call(ConstructorSignature)
Khởi tạo lớp staticinitialization(TypeSignature)
Thực hiện điều khiển ngoại lệ execution handler(TypeSignature)
Khởi tạo đối tượng initialization(ConstructorSignature) Tiền khởi tạo đối tượng preinitialization(ConstructorSignature) Thực hiện hành vi adviceexecution()
[ access specification ] aspect <AspectName>
[extends class-or-aspect-name]
[implement interface-list]
[<association-specifier>(Pointcut)]{
…aspect body }
Hình 2.7 – Cấu trúc cơ bản của một khía cạnh
Trang 25i Có định danh phạm vi truy cập (public, private, protect) cho nó Ngoài ra khía cạnh còn có thể có định danh phạm vi truy cập “privileged”, định danh này cho
phép khía cạnh có thể truy cập đến các thành viên riêng của lớp mà chúng cắt ngang,
ii Có thể khai báo trìu tượng bằng từ khóa abstract,
iii Khía cạnh không thể được thực hiện hóa trực tiếp,
iv Khía cạnh có thể kế thừa các lớp và các khía cạnh trìu tượng khác, có thể thực thi các giao diện nhưng không thể kế thừa từ các khía cạnh cụ thể (không trìu tượng),
v Khía cạnh có thể nằm trong các lớp và giao diện
Cấu trúc cơ bản của một khía cạnh được mô tả trong Hình 2.7
2.4.6 Cơ chế hoạt động của AspectJ
AspectJ compiler là thành phần trung tâm của AspectJ, nó có nhiệm vụ kết hợp các file
mã nguồn của Java với các aspect lại với nhau để tạo thành chương trình cuối cùng Quá trình dệt mã nguồn này có thể diễn ra ở các thời điểm khác nhau: compile-time, link-time và load-time:
- Compile – time: Dệt trong thời gian biên dịch là cách đơn giản nhất Mã nguồn Java và các aspect sẽ được kết hợp với nhau trước khi biên dịch mã nguồn ra dạng byte code Hay nói cách khác, trước khi biên dịch mã aspect sẽ được phân tích, chuyển đổi sang dạng mã Java và được chèn chính xác vào các vị trí đã được định nghĩa sẵn trong mã nguồn Java chính của chương trình Sau đó trình biên dịch sẽ dịch mã được dệt này sang dạng byte code AspectJ 1.0.x sử dụng cách này để dệt chương trình
- Link – time: Quá trình dệt được thực hiện khi mã nguồn Java và các aspect được biên dịch ra dạng bytecode Bộ xử lý tĩnh được sử dụng để đánh dấu các điểm gọi hàm trong mã được viết bằng java Khi một hàm được thực thi Runtime System sẽ phát hiện ra điểm nào cần gọi đến mã aspect để thực thi và khi đó mã aspect sẽ được gọi và đan vào chương trình chính AspectJ 1.1.x sử dụng cách này để dệt chương trình
- Load – time: Quá trình dệt được thực hiện khi máy ảo Java tải một class vào để chạy Theo cách này, mã nguồn Java và các aspect được biên dịch ra dạng byte code Quá trình dệt diễn ra khi classloader nạp một class Aspectj 1.5.x sử dụng cách này để dệt chương trình
Trang 262.5 Kết luận
Trong chương này luận văn đã trình bày tổng quan một số kiến thức nền được sử dụng cho các đóng góp của luận văn Mục 2.1 giới thiệu tổng quan về các phương pháp kiểm chứng hình thức và kiểm chứng tại thời điểm thực thi Mục 2.2 và 2.3 trình bày một số vấn đề trong chương trình tương tranh và sự tương tranh trong chương trình Java Mục 2.4 giới thiệu các kiến thức cơ bản về phương pháp lập trình hướng khía cạnh AOP được sử dụng để kiểm chứng sự tuân thủ ràng buộc thời gian của các thành phần trong chương trình so với đặc tả của nó
Trang 27Chương 3 Ràng buộc thời gian giữa các thành phần trong chương trình tương tranh
3.1 Giới thiệu
Ràng buộc thời gian giữa các thành phần đóng một vai trò quan trọng trong các hệ thống phần mềm đặc biệt với các hệ thống thời gian thực và hệ thống nhúng Ở chương này luận văn nghiên cứu một phương pháp kiểm chứng sự tuân thủ ràng buộc thời gian giữa các thành phần tương tranh so với đặc tả sử dụng lập trình hướng khía cạnh [1] Trong đó, ràng buộc thời gian giữa các thành phần được đặc tả bằng biểu thức chính quy thời gian Từ các đặc tả này mã aspect sẽ được tự động sinh ra đan với
mã của các thành phần để từ đó kiểm chứng sự tuân thủ ràng buộc thời gian so với đặc
tả Phương pháp này đã được thực nghiệm với nhiều chương trình khác nhau Kết quả thực nghiệm cho thấy phương pháp đã phát hiện các vi phạm về ràng buộc thời gian giữa các thành phần so với đặc tả
3.2 Bài toán kiểm chứng ràng buộc thời gian giữa các thành phần tương tranh
Giả sử một đoạn mã nguồn thực thi chương trình bao gồm cả thành phần tuần tự và thành phần tương tranh được mô tả trong Hình 3.1
public static void main(String[] args) throws Exception {
String rs1 = firstSequence(10);
String rs2 = secondSequence(10);
BlockingQueue queue = new ArrayBlockingQueue(1024);
Producer producer = new Producer(queue);
Consumer consumer = new Consumer(queue);
Thread t1 = new Thread(producer);
Thread t2 = new Thread(consumer);
secondSequence(…) Cuối cùng hai Thread t1 và t2 được thực thi song song nhau
Trang 28i Phương thức firstSequence(…) được thực hiện với đoạn thời gian đáp ứng cho
phép là [a 1 , b 1] Tiếp đó phương thức secondSequence(…) được thực hiện với
thời gian đáp ứng [a 2 , b 2] Cuối cùng hai Thread t1 và t2 (hai Thread trên sử dụng phương thức run(…) trong mỗi Thread để thực thi) được thực thi với thời
Hình 3.2 – Mô tả quá trình chạy các phương thức
Định nghĩa 3.1 (Hai sự kiện tuần tự) Hai sự kiện E 1 và E 2 được thực hiện tuần tự
nhau khi và chỉ khi t 21 ≥ t 12 hoặc t 11 > t 22
Định nghĩa 3.2 (Hai sự kiện đan xen) Hai sự kiện E 1 và E 2 được thực hiện đan xen
nhau khi và chỉ khi t 11 ∈ [t 21 , t 22 ] hoặc t 12 ∈ [t 21 , t 22 ] hoặc ngược lại t 21 ∈ [t 11 , t 12] hoặc
t 22 ∈ [t 11 , t 12]
Định nghĩa 3.3 (Hai sự kiện song song và phủ nhau) Hai sự kiện E 1 và E 2 song song
và phủ nhau khi và chỉ khi t 11 ,t 12 ∈ [t 21 , t 22 ] hoặc t 21 , t 22 ∈ [t 11 , t 12]
Định nghĩa 3.4 (Hai sự kiện song song tại cùng một thời điểm) Hai sự kiện E 1 và E 2
được thực hiện song song tại cùng một thời điểm khi và chỉ khi t 11 = t 21