הבלוג של ארתיום
בלוג על לינוקס, תוכנה חופשית, מוזיקה, סלסה, ומה לא!
מערכות הפעלה, API ומה שביניהם...
הקבוצה עובדת על פרויקט גדול מאוד שאמור להיות מתחזק במשך שנים. הפרויקט כולל רכיבים מגוונים ונדרש לעבוד בצורה מהירה ואמינה. עכשיו יש לכם בעיה: אתם לא רוצים להיות תלויים בספק יחיד או במילים אחרות לא מעוניינים ב-Vendor Lock In. לכן, אתם רוצים שהתוכנה שלכם תהיה Cross Platform: תוכלו להריץ אותה על Linux, OpenVMS, HP-UX, BSD, Win NT, Solaris ואולי עוד כמה מערכות אקזיטיות שאני שכחתי לציין. אז כיצד ניתן לעשות זאת?
אז אלו אופציות יש לכם:
- לכתוב בשפה שלא תלויה בפלטפורמה כמו Java. רק מה עם הביצועים? מה עם תלות בספק יחיד?
- לכתוב את הכל בשפה כמו Python/Perl? כנ"ל
- להשתמש בספריות Cross Platform כמו: ACE ועוד שבונות שכבת תאימות בין מערכת הפעלה לבין הקוד.
אז האופציה השלישית היא כיום הסבירה ביותר, אבל בואו נתבונן בפעולה פשוטה כמו נעילת mutex בעזרת, נגיד APR במערכות שונות:
נעבור שלב שלב:
- ב-Linux נקראת פונקציה apr_thread_mutex_lock ששייכת לספריית apr.
- היא קוראת לפונקציה pthread_mutex_lock ששייכת לספרית pthread.
- היא פונה לקריאת מערכת futex ומבצעת נעילה.
מה היה קורא אם הייתי מבצע את זה על, נגיד, OpenVMS? (לא שיש port של apr ל-VMS).
- apt_thread_mutex_lock הייתה פונה ל-pthread_mutex_lock של C RTL
- היא הייתה קוראת לפונקיית sys$enqw שמבצעת את הנעילה.
מה היה קורא ב-Solaris? בעצם משהו דומה למה שקורה ב-Linux. אבל בכל פעם היינו עוברים דרך pthread_mutex_lock.
מה מעניין פה?
שיש הרבה דרכים לממש נעילות? אולי ש-pthreads זה משהו מאוד נפוץ? או אולי הוא מאוד מוצלח כך שמשתמשים בו בהרבה מקומות? לא! מה שמעניין פה זה מדוע אנחנו זקוקים ל-apr_thread_mutex_lock? הרי בסופו של דבר כולם יפנו לאותה ספריית pthreads (כמעט כולם).
בחזרה לשאלה שלנו (קצת רקע)
בזמן האחרון אני עובד על יישום מורכב שאמור לנהל טיפול במספר סוגי קלטים ופלטים בו זמנים. בעקבות זה כתבתי קוד מסויים שאמור לבצע משימה לגמרי פשוטה ולתדהמתי גיליתי:
- הוא עובד על Linux כמו שצריך.
- ב-OpenVMS הייתי צריך לבצע כמה כיולים עדינים על מנת שזה יעבוד כמו שצריך (וזה לקח הרבה זמן להבין איפה הבעיה בכלל).
- ב-Windows נדרשה לי שעת עבודה ועזרה של מישהו שיודע Win32API בצורה מעולה על מנת לגרום לתוכנה להתקמפל ולעבוד -- דברים מעצבנים כמו קריאה ל-closesocket במקום close. או שימוש ב-send של סוקטים במקום write כללי ועוד.
אז בשביל להריץ משהו בלתי תלוי אני צריך מעטפה של ספריה כמו apr או ace על מנת לקרוא לפונקציות שהוגדרו בתקן מערכות הפעלה של POSIX...
זה נראה מאוד טבעי למי שכתב אי פעם משהו cross platform. אבל יש סתירה בעצם ההרגל הזה.
הרי ראינו שבפטפורמות שונות כל קריאה מתורגמת למשהו ברמה נמוכה יותר כמו futex ב-Linux עבור נעילת mutexים או clone עבור יצירת threads. מדוע אנחנו זקוקים לעוד שכתב תאימות למשהו שכבר מהווה שכבה כזו!
כבר יש לנו שכבת תאימות והיא -- POSIX.
אם אני כותב לפי תקן POSIX אני אוכל בצורה יחסית פשוטה לקמפל את התוכנה על פלטפרומת POSIX אחרת, באם מדובר ב-Solaris, Linux, BSD, HP-UX, Cygwin ואחרות.
פעם חשבתי, כדאי להפריד בין החלקי הקוד שתלויים בפלטפורמה לבין חלקים בלתי תלויים. לאחר הרבה חשיבה וניסיון לבנות תכנון כללי של מערכת הבנתי משהו אחר:
אני לא רוצה להפריד בין היישום לבין קריאות המערכת, כי הם יכולים להיות מושפעים אחד מהשני, במקום זה, אני רוצה לכתוב כך שהקוד ירוץ על כל מערכת, ואם היא לא תואמת POSIX אז צריך שכבת תאימות ל-POSIX. כי בין כה לכה אתה צריך שכבת תאימות לקרנל כלשהו שמוסתר בדרך כלל ב-C Runtime Library, אז מה טוב בזה שאנחנו קראנו לה ACE או apr והיא עדיין עושה את אותה העבודה?
לאחר הרבה זמן עבודה על OpenVMS וניסיונות לתכנן משהו שיעבוד גם על Windows אני מתחיל להבין יותר ויותר ש-POSIX זו שכתבת התאימות ועד כמה הרעיון של בניית ספריות בלתי תלויות בפלטפורמה הוא מגוחך בהגדרה שלו, כי בסופו של דבר, המימושים של אותן הפונקציות הסטדנרטיות יתורגמו לפעולות ספציפיות קרנל עצמו שלאו דווקא נראות למשתמש הקצה.
לא צריך ספריות Cross Platform -- צריך מערכת תואמת POSIX.
תגובות
מערכות הפעלה, API ומה שביניהם…...
הקבוצה עובדת על פרויקט גדול מאוד שאמור להיות מתחזק במשך שנים. הפרויקט כולל רכיבים מגוונים ונדרש לעבוד בצורה מהירה ואמינה. עכשיו יש לכם בעי...
שים לב שגם לינוקס לא תואמת ל-POSIX במלואה.
לגבי חלונות, אם לא השתמשת, ניתן לנסות עם SFU שכוללת גם קבצי headers וספריות: http://en.wikipedia.org/wiki/Windows_Services_for_Unix
אתה יודע, שראיתי, למשל, ש-OpenVMS תואמת POSIX (איזה תואמת איזה נעליים).
עכשיו מספר מילים על "אי תאימות". קודם כל, הרכיבים הלא תואמים הם בעיקר כל מיני רכיבים legacy כמו streams. מצד שני ל-OpenVMS ש"תואם" POSIX חסרים כל כך הרבה דברים בסיסיים כמו select שיוצא עם signal או fork ועוד.
כך שבגדול Linux, לכל צורך מעשי, תואם POSIX והרבה יותר ממערכות "תואמות" אחרות.
גבי Windows Services For UNIX לא יצא לי להשתמש בו (ואני די סקרן לגביו), ואני מקווה שהוא יהיה מספיק דומה ל-POSIX אמיתי. כרגע, בכל אופן, אין שום בעיה "להתאים קוד POSIXי" בעזרת cygwin (שזה מה שאני עושה לרוב).
בכל אופן, הרעיון הוא ששכבת תאימות POSIX הרבה יותר חשובה ונכונה מכל ספריות כמו apr או ace.
ארתיום, אני אנסה לא להיות טרול (כי אין לי כוונה כזו), אבל FPC בנוי בדיוק לתת מענה של API זהה, כאשר הקריאת מערכת שונה. מה הכוונה ?
function mutex_lock(....) {$IFDEF BSD} {BSD CODE} {$ENDIF} {$IFDEF LINUX} {LINUX CODE} {$ENDIF} וכו'... ככה הAPI זהה, רק הקוד תחתיו הוא קוד טבעי בלי הצורך להשתמש בספרייה צד שלישי ... אם תראה את הBinding הדינאמי שעשיתי עבור libhdate, אתה תראה בדיוק את זה, אני משתמש בAPI אחיד לטעון בצורה דינאמית את הספרייה והפונקציות שלה, אבל הקוד ישתמש בפנים בפונקציות השונות לפי שפה, קרי ביוניקס libdl (אם אני זוכר נכון) ובווינדוז ב LoadLibrary. שים לב שלכל אחת יש תחביר שונה, אבל בגלל שעבדתי עם היחידה ה"גלובלית" שעושה את התאימות בשבילי (שזה בסה"כ IFDEF בסופו של דבר), אז אתה רואה שהקוד שלי לא ישתנה לשום פלטפורמה שתומכת בטעינה דינאמית, למרות שהקוד מתחת כן משתנה, וכן הצורך של אותו API של מערכת ספציפית.
ד"א לדעתי זה ביצוע חזק יותר, בגלל שאין לי משהו שבאמת מנפח את הקוד לרמות תאימות מטורפות, כי בסה"כ הכל נהיה קוד טבעי בסוף, וגם אתה מקבל API זהה לכל מערכת, וככה אתה אפילו לא צריך לטפל בדברים ספציפיים מאוד (קרי מה סימן הנתיב, דבר שקיים במשתנה גלובלי, שמוחלף בזמן הידור לסביבה שאתה מעוניין בה).
ד"א לthreads, יש לך גם קוד גלובלי של RTL, שאתה בכלל לא מרגיש שהוא מבצע משהו תלוי מערכת (אלא אם אתה משתמש במערכת הפעלה שלא תומכת בזה).
לגבי ifdef-ים למיניהם -- ברור שזו לפעמים הדרך היחידה לפתור את הבעיה מבלי להסתמך על יישום צד ג'.
מה הבעיה עם ifdef? בואו נניח שאני רוצה להשתמש בקוד שלך על OpenVMS אז ספר לי כיצד בדיוק תטען את הספריה? האם זה libhdata.dll או so או OLD או EXE או עוד משהו?
תשמע, לעבוד עם ifdef זה מקור לצרות במיוחד אם המערכת מסתמכת על הרבה רכיבים שקשורים עמוק למערכת הפעלה.
מדוע? א. אתה צריך לקמפל ולבדוק את הקוד שלך על כל מערכת הפעלה. ב. אתה צריך נגישות לכל מערכות הפעלה (רישוי, כלי פיתוח חומרה ועוד). ג. אתה צריך לבצע QA על כל מערכת הפעלה כי הבאג יכול להמצא באחד ה"ifdefים" שלא בדקת בפלטפורמה אחת.
בנוסף, ההסבר שאתה נותן ל-pascal הוא תקף באותה מידה ל-C. הספריות של C סטנדרטיות ובד"כ אפשר להמיר המון קוד ממערכת למערכת בלי לשנות אותו.
עוד בעיה חשובה?
למשל, אתה הצעת להשתמש ב-mutex שונה לכל מערכת בעזרת ifdef.
אבל נסתכל: posix מספק 3 רכיבים חשובים: א. mutex -- יש לו רכיב דומה ב-win32api וזה critical section. ב. condition -- אין ב-win32api משהו דומה זה. ל-eventים ב-win32 יש סימנטיקה אחרת לחלוטין. ג. rwlock ככל הידוע לי לא קיים ב-win32 בכלל.
כמובן ניתן לממש אחד בעזרת השני, כלומר events בעזרת conditions ו-conditons ו-rwlocks בעזרת events.
כבר דבר פשוט כל כך, מהווה הבדל משמעותי מאוד בין המערכות שדורש ממך חשיבה שונה או לממש רכיב אחד בעזרת רכיבים אחרים.
האם ב-pascal יש הכל?
האם יש דברים כמו mmap? טיפול ב-mutexים שנמצאים בזכרון משותף? תורי הודעות כמו mq_XYZ? יש שכבה אחידה ל-socketים ול-file descriptors אחרים? יש pipe? יש fork? יש טיפול ב-threads? יש תמיכה ב-IO אסינכרוני?
ומה שעוד יותר חשוב האם כל הרכיבים האלה הם חלק מהסטנדרט כך שאוכל למשל לקחת Borland Pascal במקום FPC ולהשתמש בו. (הרי אני לא מעוניין ב-Vendor Lock In).
הבנת את הכוונה שלי? API של מערכת הפעלה הוא מאוד מורכב, הוא הרבה מעבר לתחום של שפות תכנות. זה הרבה יותר. לכן, יש אנשים שחשבו כיצד לעשות סדר ונוצר תקן POSIX שמאפשר לך לדבר בשפה משותפת על פלטפורמות שונות.
אם אתה כבר אומר שוינדוס לא תואמת פוסיקס איך אתה יכול להסתמך על פוסיקס כקרוס פלטפורם? אם אתה גם רוצה תמיכה בוינדוס או מערכות אחרות?
אוקי לא ירדת לסוף דעתי. 1. מרבית ה IFDEF נמצאים ברמה נמוכה יותר מהרמה שאתה כותב, כלומר משהו עשה לך את העבודה. 2. מצער אותי לשמוע שאתה כותב קוד ולא בודק אותו בכל המערכות שהוא נועד לרוץ עליהם ... 3. בד"כ אני לא מתעסק בשאלה האם קוראים לספרייה libc.so או user32.dll, בשביל זה יש לי את FPC שיודע לחפש חוקים מסויים לפי הסביבה שאליה אתה מהדר (ככה שאני אפילו לא צריך לכתוב fpc -l אלא אני יכול לציין את השם הכולל בתוך הספרייה ע"י פקודות מהדר.
לא בההכח יהיה מימוש של mmap, אלא משהו שיתן מענה דומה. יש לך למשל את המחלקה pipes אשר משתמשת בpipes בהתאם לסביבה שבה אתה רץ. אתה חושב ברמה של מערכת ספציפית, כאשר הפתרון שאני מציע לך הוא מימוש בקו התחתי (שלרוב כבר קיים עבורך) של הAPI פר מערכת, ומעל לממש API אחיד להכל. בדיוק מה שהספריות שאתה מדבר עליהם, רק כאן זה מתהדר פנימית לא בתור ספרייה.
בקשר לvendor lock, תלוי איך אתה כותב את הקוד שלך. למשל אני כותב את רוב הקוד שלי בצורה שלא יהיה מסובך להוסיף תמיכה בעוד מהדרים שונים של פסקל, אבל לא כל מה שאני כותב יעבוד בשאר מהדרים בלי קצת עבודה (ד"א ניתן לדעת בוודאות מה המהדר ולשים קוד ספציפי לאותו מהדר ואפילו גרסה ספציפית בתוך הקוד, רק אני משתמש רק במהדר מסוג סצפיפי, אז אני לא עושה את זה). ובכלל בקשר ל borland Pascal, אין כבר כיום דבר כזה. הגרסה האחרונה היתה לWindows 3 ... ככה שמן הסתם הרבה מאוד דברים לא יתמכו, גם בגלל שהמהדר והשפה עצמה התקדמה מאז רבות. אם אתה מדבר על דלפי, אז גם הוא כבר לא בפיתוח מאז דלפי 7. הדלפי שקיים היום מכיל תחביר שנראה ומריח כמו פסקל, אבל זה בכלל .NET.
הספריות הבסיסיות של FPC ד"א כן מסוגלות לרוץ בעוד מהדרים. אבל ה RTL לא. בגלל שהוא רמה אחת מעל, ומותאם להמהדר הספציפי הזה. למרות שברובו הוא מותאם גם לדלפי (המקורי ולא .net).
ד"א אם אתה רוצה לראות משהו שנכתב בדלפי וFPC עושה לו Cross Compile ללינוקס בשביל שירוץ, אז אתה מוזמן לבקר כאן: http://z505.com/powtils/idx.shtml
זה משהו שמאוד מזכיר את ה C++ CMS שלך, רק וותיק יותר בכמה וכמה שנים, וכתוב בשפת פסקל מונחת עצמים כמובן :)
סתם שתראה טיפה יותר לעומק את מה שאני מדבר עליו: http://svn.freepascal.org/svn/fpc/trunk/packages/fcl-process/src/process.pp
או למשל תמיכה ב registry, היות וזה דבר שקיים רק בווינדוז, עדיין קיבלתי תמיכה לזה בספריות גם ללינוקס וכו', רק במקום להשתמש ב WinAPI, הם משתמשים בקבצי XML: http://svn.freepascal.org/svn/fpc/trunk/packages/fcl-registry/src/registry.pp
ואני כמובן יכול להמשיך בדוגמאות, אבל אני חושב שזה משקף את מה שאני מנסה להסביר.
נדב, הבעיה עם POSIX הרבה יותר חמורה... אין שום מערכת הפעלה בעולם שתומכת בתקן כמו שצריך... לא לינוקס, לא BSD, לא ווינדוז וכו'... כולם ממשים דברים עד שזה מתנגש עם מה שהם רצו להשיג. יש לי זכרון (בגלל שזו היתה הפעם הראשונה שנתקלתי בזה), כאשר השתמשתי בקוד שהשתמש ב ioctl, והקוד נכתב בלינוקס ונושא גם בBSD, ואז גיליתי שלינוקס ממש את הפקודה לפתי התקן, וBSD לא.. ואז הגעתי למצב של interger overflow. ככה שאני לא בונה על POSIX בכלל בשביל תאימות...
תשמע, אם FPC הוא כמו Java -- שיש API זהה בכל מערכות לכל דבר אז מה טוב.
נראה לי פספסת את הכוונה. אם אני רוצה לפתח למספר פלטפורמות שונות. אז אני צריך לכתוב קוד תלוי פלטפורמה ולבדוק אותו על אותה פלטפורמה. הבעיה היא שאם אין לי אותה מבחינה פיזית. אז אין לי גם דרך לבדוק אותה.
כלומר אני לא רוצה שהקוד שלי יהיה תלוי בפלטפורמה ספציפית.
לגבי תאימות ל-POSIX, ברור שהמצב לא אידיאלי, אבל השאלה היא, אם אני רוצה להמיר יישום מפלטפורמה X ל-Y אני צריך להשקיע יום עבודה או להשקיע חודשים.
בכל אופן הנקודה העיקרית היא: לא צריך שכבות תאימות, צריך תאימות ל-POSIX.
הכוונה שאני רוצה שהוא יהיה cross platform. וד"א גם ל-windows יש שכבות תאימות ל-POSIX כמו cygwin או SFU (סתם לצורך בדיקה אחרי 5 דקות של עבודה וכמה תיקונים יחסית קטנים הצלחתי לקמפל thttpd על windows עם sfu - למרות שהוא רחוק משלמות)
הוסף תגובה:
חובה לאפשר JavaScript כדי להגיב.