10.6 פרויקט סיכום פרויקט
פרויקט סיכום - מערכת פלאגינים - plugin system¶
הקדמה¶
בפרויקט הזה נבנה מערכת פלאגינים (plugins) בשפת C. הפרויקט מחבר את כל מה שלמדנו בפרק 10:
- מצביעי פונקציות (פרק 10.5) - ממשק הפלאגין הוא struct עם מצביעי פונקציות
- טעינה דינמית (פרק 5.10 + 10.5) - dlopen/dlsym לטעינת ספריות בזמן ריצה
- פעולות אטומיות (פרק 10.4) - מונה סטטיסטיקות thread-safe
- טריקים ביטיים (פרק 10.3) - דגלים למצב הפלאגין
הרעיון: תוכנית ראשית (host) שטוענת פלאגינים מתיקייה, מפעילה אותם על קלט, ומנהלת סטטיסטיקות. כל פלאגין הוא ספרייה משותפת (.so) שמממשת ממשק קבוע.
הפרויקט בנוי בשלבים - כל שלב מוסיף יכולת חדשה.
ממשק הפלאגין - plugin interface¶
כל פלאגין חייב לחשוף struct מסוג plugin_t דרך פונקציה בשם plugin_get_info:
/* plugin_api.h - הממשק המשותף בין ה-host לפלאגינים */
#ifndef PLUGIN_API_H
#define PLUGIN_API_H
#define PLUGIN_API_VERSION 1
typedef struct plugin {
const char *name; /* שם הפלאגין */
const char *description; /* תיאור קצר */
int api_version; /* גרסת API - חייב להיות PLUGIN_API_VERSION */
int (*init)(void); /* אתחול, מחזיר 0 בהצלחה */
void (*cleanup)(void); /* ניקוי משאבים */
char *(*process)(const char *input); /* עיבוד קלט, מחזיר תוצאה */
} plugin_t;
/* כל פלאגין חייב לממש את הפונקציה הזו */
typedef plugin_t *(*plugin_get_info_fn)(void);
#endif
שמרו את הקובץ הזה כ-plugin_api.h. גם ה-host וגם הפלאגינים ישתמשו בו.
שלב 1 - כתיבת פלאגינים¶
כתבו שלושה פלאגינים, כל אחד בקובץ נפרד. כל פלאגין מממש את הממשק בצורה שונה.
פלאגין 1 - uppercase - המרה לאותיות גדולות:
/* plugin_upper.c */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#include "plugin_api.h"
static int upper_init(void) {
printf("[upper] initialized\n");
return 0;
}
static void upper_cleanup(void) {
printf("[upper] cleaned up\n");
}
static char *upper_process(const char *input) {
char *result = strdup(input);
if (!result) return NULL;
for (int i = 0; result[i]; i++)
result[i] = toupper(result[i]);
return result;
}
static plugin_t plugin = {
.name = "uppercase",
.description = "converts text to uppercase",
.api_version = PLUGIN_API_VERSION,
.init = upper_init,
.cleanup = upper_cleanup,
.process = upper_process,
};
plugin_t *plugin_get_info(void) {
return &plugin;
}
פלאגין 2 - reverse - היפוך מחרוזת:
כתבו פלאגין שמקבל מחרוזת ומחזיר אותה הפוכה. ממשו את reverse_process באופן דומה.
פלאגין 3 - wordcount - ספירת מילים:
כתבו פלאגין שמקבל מחרוזת ומחזיר מחרוזת עם מספר המילים (למשל, "3 words").
קומפילציה של הפלאגינים:
gcc -shared -fPIC -o plugins/plugin_upper.so plugin_upper.c
gcc -shared -fPIC -o plugins/plugin_reverse.so plugin_reverse.c
gcc -shared -fPIC -o plugins/plugin_wordcount.so plugin_wordcount.c
שימו לב: צרו תיקיית plugins/ לפני הקומפילציה.
שלב 2 - טעינת פלאגינים¶
כתבו את התוכנית הראשית שסורקת תיקיית plugins/, מוצאת קבצי .so, וטוענת כל אחד עם dlopen.
הנה קוד התחלתי:
/* host.c */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <dlfcn.h>
#include <dirent.h>
#include "plugin_api.h"
#define MAX_PLUGINS 32
struct loaded_plugin {
void *handle; /* dlopen handle */
plugin_t *info; /* מידע על הפלאגין */
};
struct loaded_plugin plugins[MAX_PLUGINS];
int plugin_count = 0;
int load_plugin(const char *path) {
void *handle = dlopen(path, RTLD_LAZY);
if (!handle) {
fprintf(stderr, "dlopen(%s): %s\n", path, dlerror());
return -1;
}
dlerror(); /* ניקוי שגיאות */
plugin_get_info_fn get_info = dlsym(handle, "plugin_get_info");
if (!get_info) {
fprintf(stderr, "dlsym: %s\n", dlerror());
dlclose(handle);
return -1;
}
plugin_t *info = get_info();
if (info->api_version != PLUGIN_API_VERSION) {
fprintf(stderr, "plugin %s: incompatible API version %d (expected %d)\n",
path, info->api_version, PLUGIN_API_VERSION);
dlclose(handle);
return -1;
}
/* TODO: קראו ל-info->init() ובדקו את ערך ההחזרה */
plugins[plugin_count].handle = handle;
plugins[plugin_count].info = info;
plugin_count++;
printf("loaded plugin: %s (%s)\n", info->name, info->description);
return 0;
}
int scan_plugins(const char *dir_path) {
/* TODO: פתחו את התיקייה עם opendir,
עברו על הקבצים עם readdir,
טענו כל קובץ שמסתיים ב-.so */
return 0;
}
מה צריך לממש:
1. השלימו את scan_plugins - סרקו תיקייה, מצאו קבצי .so, טענו כל אחד עם load_plugin
2. קראו ל-init() של כל פלאגין אחרי הטעינה
3. טפלו בשגיאות - פלאגין שנכשל באתחול לא צריך להישמר ברשימה
רמז: השתמשו ב-
opendir,readdir,closedirמ-<dirent.h>. כדי לבדוק אם קובץ מסתיים ב-.so, השתמשו ב-strstrאוstrcmpעל הסיומת.
שלב 3 - עיבוד קלט¶
הוסיפו לולאה ראשית ל-host שקוראת שורות מהמשתמש ומעבירה כל שורה לכל הפלאגינים:
void process_input(const char *input) {
for (int i = 0; i < plugin_count; i++) {
char *result = plugins[i].info->process(input);
if (result) {
printf(" [%s]: %s\n", plugins[i].info->name, result);
free(result);
}
}
}
int main(int argc, char **argv) {
const char *plugin_dir = (argc > 1) ? argv[1] : "./plugins";
scan_plugins(plugin_dir);
if (plugin_count == 0) {
printf("no plugins loaded\n");
return 1;
}
printf("\nready. enter text (or 'quit' to exit):\n");
char input[1024];
while (fgets(input, sizeof(input), stdin)) {
/* הסרת newline */
input[strcspn(input, "\n")] = '\0';
if (strcmp(input, "quit") == 0)
break;
process_input(input);
}
/* ניקוי */
for (int i = 0; i < plugin_count; i++) {
plugins[i].info->cleanup();
dlclose(plugins[i].handle);
}
return 0;
}
דוגמת הרצה:
loaded plugin: uppercase (converts text to uppercase)
loaded plugin: reverse (reverses text)
loaded plugin: wordcount (counts words)
ready. enter text (or 'quit' to exit):
hello world
[uppercase]: HELLO WORLD
[reverse]: dlrow olleh
[wordcount]: 2 words
quit
שלב 4 - סטטיסטיקות עם atomics¶
הוסיפו מונים אטומיים לכל פלאגין כדי לעקוב אחרי השימוש:
#include <stdatomic.h>
struct loaded_plugin {
void *handle;
plugin_t *info;
atomic_int call_count; /* כמה פעמים process נקרא */
atomic_long total_input_len; /* סך אורך הקלט שעובד */
};
- אתחלו את המונים ל-0 בזמן הטעינה.
- בכל קריאה ל-process, הגדילו את
call_countב-1 ואתtotal_input_lenבאורך הקלט. - השתמשו ב-
memory_order_relaxed(כי זה רק מונים). - הוסיפו פקודה "stats" שמדפיסה את הסטטיסטיקות של כל פלאגין.
שלב 5 - עיבוד מרובה threads¶
שנו את process_input כך שכל פלאגין ירוץ ב-thread נפרד:
- לכל פלאגין, צרו thread שמריץ את
process. - חכו לכל הthreads עם
pthread_join. - הדפיסו את התוצאות.
#include <pthread.h>
struct process_args {
struct loaded_plugin *plugin;
const char *input;
char *result;
};
void *process_thread(void *arg) {
struct process_args *pa = arg;
/* TODO: קראו ל-process, שמרו תוצאה,
עדכנו מונים אטומיים */
return NULL;
}
ודאו שהמונים האטומיים עובדים נכון גם כשכמה threads מעדכנים אותם בו-זמנית.
Makefile¶
הנה Makefile להמליץ על תהליך הבנייה:
CC = gcc
CFLAGS = -Wall -Wextra -g
LDFLAGS = -ldl -lpthread
# תוכנית ראשית
host: host.c plugin_api.h
$(CC) $(CFLAGS) -o host host.c $(LDFLAGS)
# פלאגינים
plugins/plugin_upper.so: plugin_upper.c plugin_api.h
mkdir -p plugins
$(CC) $(CFLAGS) -shared -fPIC -o $@ $<
plugins/plugin_reverse.so: plugin_reverse.c plugin_api.h
mkdir -p plugins
$(CC) $(CFLAGS) -shared -fPIC -o $@ $<
plugins/plugin_wordcount.so: plugin_wordcount.c plugin_api.h
mkdir -p plugins
$(CC) $(CFLAGS) -shared -fPIC -o $@ $<
# בנייה של הכל
all: host plugins/plugin_upper.so plugins/plugin_reverse.so plugins/plugin_wordcount.so
clean:
rm -f host plugins/*.so
.PHONY: all clean
שימוש:
רעיונות להרחבה¶
דגלי מצב עם ביטים:
הוסיפו שדה unsigned int flags ל-loaded_plugin עם דגלים כמו:
- PLUGIN_LOADED (1 << 0) - נטען בהצלחה
- PLUGIN_INITIALIZED (1 << 1) - אותחל
- PLUGIN_ENABLED (1 << 2) - מופעל
- PLUGIN_ERROR (1 << 3) - שגיאה
הוסיפו פקודות "enable"/"disable" שמדליקות/מכבות את דגל ENABLED, ועיבוד רק לפלאגינים שהדגל שלהם דלוק.
תיעדוף פלאגינים:
הוסיפו שדה priority ל-plugin_t. מיינו את הפלאגינים לפי עדיפות (עם qsort ופונקציית השוואה) והריצו אותם בסדר.
שרשור פלאגינים - pipeline:
במקום להריץ כל פלאגין על הקלט המקורי, העבירו את הפלט של פלאגין אחד כקלט לפלאגין הבא.
הוספת פלאגין בזמן ריצה:
הוסיפו פקודה "load" שמקבלת נתיב לso חדש וטוענת אותו תוך כדי ריצה.
בהצלחה!