3. Khung ứng dụng libmain và thư viện timer
3.1. Khung ứng dụng libmain
Trên môi trường Linux, sự kiện gửi tới chương trình là các signal (ngắt), hoặc một file descriptor chuyển sang trạng thái readable (BSD). Lúc này hàm MapMessage() được sử dụng để ánh xạ các signal, còn MapHandle() dành cho các file descriptor (hiện LIBMAIN không hỗ trợ nhận biết các trạng thái writable và exception).
Ghi chú: Theo lệ thường, hàm xử lý signal sẽ được thực hiện trong một ngữ cảnh ưu tiên so với chương trình chính, điều này thường gây phiền phức về mặt đồng bộ. Để tránh vấn đề này, phiên bản LIBMAIN trên Linux tổ chức một hàng đợi cho phép xử lý tuần tự các signal trong chương trình chính.
Định dạng của chương trình chính viết trên Libmain:
Chương trình chính được implement trong hàm sau:
int AppMain (MODULE *pModList, int (* fnStartup)(void)); Thuật toán của khung chương trình chính như sau:
- Khởi tạo nội bộ.
- Khởi tạo (setup) các module của chương trình theo danh sách trong mảng pModList (xem mục khia báo các module), nếu có lỗi thì thông báo và kết thúc.
32
- Gọi hàm khởi động chương trình qua con trỏ fnStartup, nếu có lỗi thì thông báo và kết thúc.
- Vòng lặp chờ và xử lý sự kiện, lập lịch cho các timer trong thư viện timer, lặp cho tới khi bị dừng bởi hàm QuitProgram.
- Giải phóng (cleanup) theo các hàm cleanup đã đăng ký.
Trong lời gọi đến AppMain() cần phải chỉ ra hàm startup để chương trình chính gọi khi khởi động (sau khi đã khởi tạo các module). Hàm này có khai báo dạng như sau:
int AppStartup();
AppMain cần được gọi trong main() ví dụ như sau: int main() // program entry-point
{
pszAppName = "My Program";
return AppMain(ModList, AppStartup); }
Ghi chú: Biến toàn cục char *pszAppName cần được thiết lập để các thông báo lỗi được hiển thị đúng nhằm giúp quá trình gỡ lỗi được đơn giản hơn.
Khai báo các module:
Mỗi module trong chương trình được định nghĩa bằng cấu trúc sau: struct MODULE {
char *pszName; // Name of module
SETUPFN pfnSetup; // Setup function of module int iParam; // Parameter passed to setup function };
Trong đó trường pfnSetup là một con trỏ tới hàm khởi tạo module. Hàm này phải có prototype như sau:
int FuncName(int iParam);
lưu ý iParam có thể là một con trỏ trỏ đến vùng nhớ chứa dữ liệu cấu hình của module và được ép kiểu sang số nguyên 32 bit.
Danh sách các module của chương trình cần được khai báo dưới dạng mảng và truyền địa chỉ của mảng này cho hàm AppMain (tham số thứ nhất):
MODULE ModList[] = {
{ "LteUePhy", LtePhyInit, Phy_Config }, { "LteUeMac", LteMacInit, Mac_Config },
33
{ "LteUeRlc_ DTCH ", LteRlcInit, RlcDtch_Config }, ...
{ "moduleN", SetupFnN, paramN }, MODLIST_END
};
Hàm khởi tạo của các module sẽ được gọi lần lượt theo thứ tự xuất hiện trong mảng ModList[] với địa chỉ hàm trong trường pfnSetup, tham số đưa vào là trường iParam tương ứng. Nếu thành công, hàm cần trả về 0 (zero), còn nếu lỗi thì trả về giá trị âm.
Trong quá trình khởi tạo, thông thường module sẽ ánh xạ (đăng ký) các hàm (handler) của riêng nó với các sự kiện. Theo các ánh xạ này, mỗi khi có sự kiện xảy ra chương trình chính sẽ tự động gọi các handler tương ứng. Lưu ý là một signal được phép ánh xạ tới nhiều handler, khi đó các handler sẽ được gọi ngược với thứ tự đăng ký.
Ánh xạ sự kiện:
Hàm xử lý sự kiện có prototype như sau (EVTHDLR): void ev_handler(int param);
Các sự kiện được ánh xạ bằng các hàm MapMessage và MapHandle. Các trường hợp sử dụng cho từng môi trường và từng loại sự kiện như sau:
Event Param Function Multi-map
Signal signum MapMessage(); Yes File descriptor specified MapHandle(); No
MapMessage được dùng để ánh xạ các signal. Trên Linux, các signal được xếp vào hàng đợi để mô phỏng message queue, do đó ngoài khả năng đón các signal từ kernel, chương trình còn có thể gửi các message tự định nghĩa giữa các module. Có thể đăng ký nhiều handler cho một message/signal, khi có sự kiện thì các handler sẽ được gọi ngược với thứ tự đăng ký.
int MapMessage(int message, EVTHDLR handler); int MapHandle(int fd, EVTHDLR handler, int param);
Lưu ý trên Linux, MapHandle cho phép ánh xạ sự kiện readable của các file descriptor. Để hủy đăng ký các handler, sử dụng các hàm sau:
34 int UnmapHandle(int fd);
Các biến global và hàm tiện ích:
Các biến được dùng bởi AppMain được tóm tắt như sau:
char *pszAppName: Trỏ tới xâu chứa tên chương trình. Nếu chương trình có lỗi, AppMain sẽ sử dụng con trỏ này để tạo nên thông báo lỗi.
char *pszAction: Trỏ tới xâu chứa hành động hiện thời, dùng cho việc xác định lỗi. Trong hàm khởi động chương trình hoặc trong các hàm khởi tạo module, nên thiết lập biến này trước mỗi thao tác, nếu thao tác đó lỗi thì return một giá trị âm. AppMain cũng sử dụng biến này cùng với giá trị return để tạo ra thông báo lỗi.
char *pszError: Trỏ tới xâu chứa nội dung lỗi (có thể là NULL) nếu không chỉ ra. Trong quá trình khởi tạo module và startup chương trình, nếu có lỗi thì pszError cũng sẽ góp phần tạo nên thông báo lỗi. Nếu startup thành công thì pszError được tự động thiết lập về NULL. Khi chương trình kết thúc, nếu pszError khác NULL thì AppMain sẽ coi như có một lỗi run-time và cũng sinh ra thông báo lỗi.