> Proseslerin Çevre Değişkenleri: İşletim sistemleri dünyasında sıkça karşımıza çıkan önemli bir kavram da "çevre değişkenleri (environment variables)" denilen kavramdır. Sistem programcısının bu kavramı bilmesi ve gerektiğinde bundan faydalanması önemlidir. Biz de bu bölümde "proseslerin çevre değişkenleri" üzerinde duracağız. Proseslerin çevre değişkenleri "anahtar-değer (key-value)" çiftlerini tutan, anahtar verildiğinde ona karşı gelen değerin elde edildiği sözlük tarzı bir listedir. Anahtarlar ve değerler birer yazı (string) biçimindedir. Çevre değişkenleri konusunda söz konusu anahtar yazıya "çevre değişkeni (environment variable)" ona karşılık gelen yazıya da "çevre değişkeninin değeri" denilmektedir. * Örnek 1, Bir çevre değişkeni "CITY" isminde olabilir. Ona karşı gelen değer de "Ankara" olabilir. Çevre değişkenleri ilk bakışta "basit bir sözlük veri yapısı" gibi düşünülmektedir. Konuya yeni başlayan kişiler böyle bir veri yapısının neden işletim sistemi düzeyinde oluşturulduğunu neden kütüphane fonksiyonlarıyla oluşturulmadığını merak etmektedir. Gerçekten de pek çok programlama dilinin standart kütüphanesinde ya da bizzat sentaks ve semantik yapısı içerisinde sözlük (dictiobary) tarzı veri yapıları hazır bir biçimde bulunmaktadır. Genellikle işletim sistemleri (örneğin Windows, Linux, macOS) prosesin çevre değişkenlerini "anahtar-değer" çiftleri biçiminde liste tarzı bir veri yapısında saklamaktadır. Çevre değişkenleri için bu sistemlerde prosese özgü bellek bölgelerei bulundurulmaktadır. Yani çevre değişkenleri pek çok işletim sisteminde proses kontrol blokta değil bizzat prosesin bellek alanında tutulmaktadır. Prosesin çevre değişken listesinin tutulduğu alan İngilizce genellikle "environment block" biçiminde isimlendirilmektedir. İşletim sisteminde prosesin çevre değişkenleri default durumda genellikle proses yaratılırken üst prosesten alt prosese aktarılmaktadır. Yani bir programı hangi program çalıştırıyorsa çalıştırılan program (alt process) çevre değişkenlerini çalıştıran programdan (üst proses) almaktadır. Ancak program çalışmaya başladıktan sonra programcı bu çevre değişkenleri üzerinde değişiklikler yapabilmektedir. * Örnek 1, Biz Linux ya da macOS sistemlerinde komut satırından bir program çalıştırdığımızda çalıştırdığımız programın çevre değişkenleri onu çalıştıran komut satırı programında (tipik olarak bash) aktarılmaktadır. Aynı dırım Windows sistemlerinde de böyledir. Windows sistemlerinde bir simgeye çift tıklayarak bir programı çalıştırdığımızda çalıştırılan programın çevre değişkenleri masaüstü prossi olan "explorer.exe" isimli üst prosesten alınmaktadır. Bir sistem programcısının çevre değişikenleri üzerinde bazı işlemleri yapabiliyor olması gerekmektedir. İzleyen paragraflarda çevre değişkenlerine ilişkin bazı önemli ilemlerin nasıl yapıldığı üzerinde duracağız. Tabii çevre değişkenleri programlana dilinden bağımzıs aşağı seviyeli bir konu olduğu için ilgili sistemdeki düşük seviyeli fonksiyonlarla gerçekleştirilmektedir. Çevre değişkenleri üzerinde işlemler Windows sistemlerinde API fonksiyonlarıyla, UNIX/Linux ve macOS sistemlerinde ise POSIX fonksiyonlarıyla yapılmaktadır. Çevre değişkenlerinin (yani anahtarların) Windows sistemlerinde büyük harf küçük harf duyarlılığı yoktur. Ancak UNIX/Linux sistemlerinde ve macOS sistemlerinde büyük harf küçük harf duyarlılığı vardır. Şimdi de proseslerin çevre değişkenlerine ilişkin fonksiyonları görelim: >> Standart C Fonksiyonları: >>> "getenv" : Bir çevre değişkeninin anahtarı verildiğinde onun değerini elde etmek için getenv isimli bir standart C fonksiyonu bulundurulmuştur. Bu fonksiyon genel olarak ilgili sistemdeki daha aşağı seviyeli, fonksiyonları çağırarak işlemi yapmaktadır. getenv fonksiyonunun prototipi şöyledir: #include char *getenv(const char *name); Fonksiyon parametre olarak çevre değişkenin ismini alır. Çevre değişkeninin değerinin bulunduğu char türden dizinin adresine geri döner. Fonksiyonun geri döndürdüğü adresteki dizi statik biçimde tahsis edilmiştir. Dolayısıyla güvenli bir biçimde kullanılabilir. Ancak bu adresteki dizi üzerinde değişiklik yapılmamalıdır. Eğer proseste ilgili çevre değişkeni yoksa fonksiyon NULL adresle geri dönmektedir. Aşağıdaki örnekte komut satırı argümanı ile verilen çevre değişkeninin değeri ekrana (stdout dosyasına) yazdırılmaktadır. * Örnek 1, #include #include int main(int argc, char *argv[]) { char *value; if (argc != 2) { fprintf(stderr, "wrong number of arguments!..\n"); exit(EXIT_FAILURE); } if ((value = getenv(argv[1])) == NULL) { fprintf(stderr, "environment variable not found: %s\n", argv[1]); exit(EXIT_FAILURE); } puts(value); return 0; } >> UNIX/Linux (POSIX) Fonksiyonları: Bu tip fonksiyonları ele almadan önce UNIX türevi işletim sisteminin çevre değişkenlerini bellekte nasıl tuttuğunu açıklamak istiyoruz. Çevre değişkenleri için "environ" isminde global bir göstericiyi gösteren gösterici bulundurulmaktadır. Bu environ değişkeni bir gösterici dizisini göstermektedir. Gösterici dizisinin sonunda NULL adres vardır. environ (char **) -----> adres (char *) -----> "anahtar=değer" adres (char *) -----> "anahtar=değer" adres (char *) -----> "anahtar=değer" ... adres (char *) -----> "anahtar=değer" adres (char *) -----> "anahtar=değer" NULL Örneğin yukarıdaki çevre değişken bloğuna anahtarı "CITY" olan değeri "ankara" olan bir çevre değişkeni ekleyelim. environ dizisi şu hale gelecektir: environ (char **) -----> adres (char *) -----> "anahtar=değer" (malloc ile tahsis edilmiş) adres (char *) -----> "anahtar=değer" (malloc ile tahsis edilmiş) adres (char *) -----> "anahtar=değer" (malloc ile tahsis edilmiş) ... adres (char *) -----> "anahtar=değer" (malloc ile tahsis edilmiş) adres (char *) -----> "anahtar=değer" (malloc ile tahsis edilmiş) YENİ EKLENEN ADRES -----> "CITY=ankara" (burası malloc ile tahsis ediliyor) NULL Ancak UNIX/Linux sistemlerinde iş bu "environ" isimli göstericinin extern bildirimi herhangi bir başlık dosyasında bulundurulmamıştır. Bu nedenle programcının environ değişkeninin extern bildirimini kendisinin yapması gerekmektedir: extern char **environ; Tabii biz bu biçimde eçre değişkenlerini elde edeken onları tek bir yazı halinde "anahtar=değer" biçiminde elde edeceğiz. Anahtarla değerleri '=' karaketerini dikkate alarak ayrıştırabilirsiniz. Ayrıştırma işlemini yaparken geçici biçimde '=' karakterini '\0' ile değiştirebilirsiniz. Ancak çevre değişken bloğunu mümkün olduğunca değiştirmeye çalışmayınız. * Örnek 1, Aşağıdaki UNIX/Linux sistemlerinde prosesin tüm çevre değişkenleri elde edilmiştir. #include #include #include extern char **environ; int main(void) { char *str; for (int i = 0; environ[i] != NULL; ++i) puts(environ[i]); puts("--------------------------"); for (int i = 0; environ[i] != NULL; ++i) { if ((str = strchr(environ[i], '=')) != NULL) { *str = '\0'; printf("%s --> %s\n", environ[i], str + 1); *str = '='; } } return 0; } Şimdi de bu sistemdeki fonksiyonları inceleyelim: >>> "getenv" : UNIX/Linux sistemlerinde ve macOS sistemlerinde getenv aynı zamanda bir POSIX fonksiyonudur. (Aslında tüm standart C fonksiyonları aynı zamanda bir POSIX fonksiyonu kabul edilmektedir.) Dolayısıyla bu sistemlerde getenv zaten düşük seviyeli bir fonksiyondur. >>> "setenv" : UNIX/Linux sistemlerinde ve macOS sistemlerinde program çalışırken prosesin çevre değişkenine bir ekleme yapmak için setenv isimli POSIX fonksiyonu kullanılmaktadır. Tabii bu fonksiyonla eklenen çevre değişkenleri kalıcı değildir. Proses sonlandığında prosesin bütün çevre değişkenleri zaten yok edilmektedir. setenv fonksiyonunun protoripi şöyledir: #include int setenv(const char *envname, const char *envval, int overwrite); Fonksiyonun birinci parametresi çevre değişkeninin ismini (anahtar değeri), ikinc parametresi ise değerini almaktadır. Son parametre sıfır ya da sıfır dışı bir değer biçiminde girilir. Bu parametre sıfır dışı bir değer rolarak geçilirse söz konusu çevre değişkeninin olduğu durumda artık yeni değer set edilir. Bu parametre 0 geçilirse bu durumda çevre değişkeni varsa fonksiyon yine başarılı olur ancak çevre değişkeninin değeri değişmez. Aşağıdaki örnekte program çevre değişkeninin ismini vedeğerini komut satırı argümanlarıyla almıştır. Önce eçre değişkenini set etmiş sonra da get ederek yazdırmıştır. * Örnek 1, #include #include void exit_sys(const char *msg); int main(int argc, char *argv[]) { char *value; if (argc != 3) { fprintf(stderr, "wrong number of arguments!..\n"); exit(EXIT_FAILURE); } if (setenv(argv[1], argv[2], 1) == -1) exit_sys("setenv"); if ((value = getenv(argv[1])) == NULL) { fprintf(stderr, "environment variable not found: %s\n", argv[1]); exit(EXIT_FAILURE); } puts(value); return 0; } void exit_sys(const char *msg) { perror(msg); exit(EXIT_FAILURE); } >>> "putenv" : putenv isimli POSIX fonksiyonu da prosesin çevre değişkenlerine ekleme yapmak için kullanılabilir. Fonksiyonun prototipi şöyledir: #include int putenv(char *string); Fonksiyon parametre anahtar=değer" biçiminde yazının adresini almaktadır. Alında putenv fonksiyonu yukarıda açıkladığımız çevre değişkenlerine ek yaparken environ göstericisini gösterdiği yeri malloc ile tahsis etmemktedir. Doprudna bizim verdiğimiz adresi o gösterisi dizisine yazmaktadır. Dolayısıyla setenv fonksiyonundan farklı bir çalışması vardır. Örneğin: char buf[] = "COUNTRY=turkey"; putenv(buf); Bu işlemi yaptıktan sonra çevre değişken bloğu aşağıdaki gibi bir hale gelecektir: environ (char **) -----> adres (char *) -----> "anahtar=değer" adres (char *) -----> "anahtar=değer" adres (char *) -----> "anahtar=değer" ... adres (char *) -----> "anahtar=değer" adres (char *) -----> "anahtar=değer" YENI EKLENEN ADRES (buf) -----> "COUNTRY=turkey" NULL Dolayısıyla putenv fonksiyonu ile verilen adresin program sonlanana kadar yaşıyor olması gerekmektedir. putenv fonksiyonu başarı durumunda 0 değerine, başarısızlık durumunda sıfır dışı bir değere geri dönmektedir. putenv fonksiyonu ile olan bir çevre değişkeninin değerini de değiştirebiliriz. * Örnek 1, #include #include void exit_sys(const char *msg); int main(void) { char *value; if (putenv("CITY=istanbul") != 0) /* string'ler statik ömürlüdür */ exit_sys("putenv"); if ((value = getenv("CITY")) == NULL) { fprintf(stderr, "cannot find environment variable!..\n"); exit(EXIT_FAILURE); } puts(value); return 0; } void exit_sys(const char *msg) { perror(msg); exit(EXIT_FAILURE); } >>> "unsetenv" : UNIX/Linux sistemlerinde çevre değişkenlerinin silinmesi için unsetenv POSIX fonksiyonu kullanılmaktadır. Fonksiyonun prototipi şöyledir: #include int unsetenv(const char *name); Fonksiyon silinecek çevre değişkeninin ismini (anahtarını) parametre olarak alır ve siler. unsetenv fonksiyonu başarı durumunda 0 değerine, başarısızlık durumunda -1 değerine geri dönmektedir. >>> "clearenv" : Prosesin tüm çevre değişken bloğunu yok etmek için UNIX/Linux sistemlerinde clearenv POSIX fonksiyonu ile yapılmaktadır. Fonksiyonun prototipi şöyledir: #include int clearenv(void); Fonksiyon başarı durumunda 0 değerine başarısızlık durumunda sıfır dışı bir değere (-1 değerine değil) geri dönmektedir. Bu sistemlerde proseslerin çevre değişkenleri ile alakalı kabuk komutları ise şunlardır: >>> "env" : Bu kabuk komutu kabuğun çevre değişken listesini tıpkı yukarıda yazdığımız programda aolduğu gibi görüntülemektedir. Programcılar kendi programlarına aktarılacak çevre değişkenlerini bu komutla görüntüleyebilirler. >>> "export" : Bu kabuk komutu ise kabuk programının kendi çevre değişken listesine ekleme yapmak için kullanılmaktadır. Komutun genel biçimi şöyledir: export değişken=değer Örneğin: export CITY=Ankara Eğer değer boşluk içeriyorsa tırnaklanması gerekir. Örneğin: export CITY="NewYork City" Değişken bir kere export edildikten sonra artık değeri export yazılmadan da değiştirilebilir. Örneğin: CITY=Trabzon Aynı işlem Windows sistemlerinde set komutuyla u yapılmaktadır. Örneğin: set CITY=Ankara Bu sistemlerde değeri değiştirmek için yeniden set kullanmak zorunludur. UNIX/Linux sistemlerinde komut satırında "$isim" biçimindeki sentaks "bunu çevre değişkeninin değeri ile değiştir" anlamına gelmektedir. Örneğin: $ export CITY=ankara $ $CITY ankara: komut bulunamadı Burada $CITY yazılıp ENTER tuşuna basıldığnda sanki "ankara" yazılıp ENTER tuşuna basılmış gibi bir etki oluşmaktadır. >>> "echo" : Kabuktaki bu komut, yazıları ekrana yazdırmak için kullanılmaktadır. Örneğin: $ echo bugün hava çok güzel bugün hava çok güzel O halde bir eçvre değişkeninin değerini ekrana "echo $isim" komutunu uygulayarak yazdırabiliriz. Örneğin: $ echo $CITY ankara Şimdi de bu kabuk komutlarının kullanımına ilişkin bir örnek verelim: * Örnek 1, Aşağıdaki örnekte de bir çevre değişkeninin değerinin sonuna ekeleme yaapalım: $ export CITY=Ankara $ echo $CITY Ankara $ CITY=$CITY"-Istanbul" $ echo $CITY Ankara-Istanbul Eğer bir çevre değişkeni yoksa kabuk programı, $isim yerine boşluk karakteri yerleştirmektedir. Yani bu durum bir hataya yol açmamaktadır. >> Windows Sistemlerinde: >>> "GetEnvironmentVariable" : Windows sistemlerinde prosesin çevre değişkenini elde edebilmek için "GetEnvironmentVariable" isimli bi API fonksiyonu kullanılmaktadır. getenv standart C fonksiyonu Windows sistemlerinde aslında bu API fonksiyonu kullanılarak yazılmıştır. GetEnvironmentVariable fonksiyonunun prototipi şöyledir: DWORD GetEnvironmentVariable( LPCTSTR lpName, LPTSTR lpBuffer, DWORD nSize ); Fonksiyonunun birinci parametresi aranacak çevre değişkenini belirtmektedir. İkinci parametre çevre değişkeninin değerinin yerleştirileceği char türden dizinin adresini almaktadır. Üçüncü parametre ise bu dizinin uzunluğunu alır. Fonksiyon başarı durumunda diziye yerleştirilen karakter sayısına geri dönmektedir. Bu sayıya null karakter dahil değildir. Eğer verilen dizinin uzunluğu yetersiz olursa fonksiyon dizi için gereken uzunluğu (null karakter dahil olacak biçimde) bize verir. Windows sistemlerinde bir çevre değişkeninin maksimum değeri 32768 karakter olabilmektedir. Fonksiyonda hata kontrolü yapılırken bir noktaya dikkat etmek gerekir. Bir eçvre değişkeni var olduğu halde değeri boş olabilir. Bu durumda GetEnvironmentVariable fonksiyonu yine 0 değerine geri dönecektir. O halde fonksiyonun 0 değerine dönmesinin iki gerekçesi olabilir. Birincisi çevre değişkenini bulamaması, ikincisi çevre değişkenini bulması ancak onun değerinin boş olması. Bu nedenle fonksiyon 0 ile geri döndüğü zaman GetLastError fonksiyonu ile durum tespit edilmelidir. Aşağıda GetEnvironmentVariable fonksiyonunun kullanımına bir örnek verilmiştir. * Örnek 1, #include #include #include #define BUFFER_SIZE 4096 int main(void) { char value[BUFFER_SIZE]; DWORD dwResult; dwResult = GetEnvironmentVariable("PATH", value, BUFFER_SIZE); if ((dwResult == 0 && GetLastError() == ERROR_ENVVAR_NOT_FOUND)) { fprintf(stderr, "envirionment variable not found!..\n"); exit(EXIT_FAILURE); } if (dwResult > BUFFER_SIZE) { fprintf(stderr, "buffer too small, requşred size: %u\n", dwResult); exit(EXIT_FAILURE); } puts(value); return 0; } >>> "SetEnvironmentVariable" : Windows sistemlerinde çevre değişkenini değiştirmek ve çevre değişken bloğuna yeni bir çevre değişkeni eklemek için SetEnvironmentVariable isimli API fonksiyonu kullanılmaktadır. Fonksiyonun prototipi şöyledir: BOOL SetEnvironmentVariable( LPCTSTR lpName, LPCTSTR lpValue ); Fonksiyonun birinci parametresi çevre değişkeninin ismini (yani anahtarını), ikinci parametresi ise değerini almaktadır. Fonksiyon başarı durumunda sıfır dışı bir değere, balarısızlık durumunda 0 değerine geri dönmektedir. Eğer ilgili çevre değişkeni zaten varsa fonksiyon onun değerini değiştirmektedir. Fonksiyonun ikinci parametresi NULL adres geçilebilir. Bu durumda ilgili eçvre değişkeni silinmektedir. SetEnvironmentVariable fonksiyonunda ikinci parametre NULL adres geçilirse ilgili çevre değişkeni silinmektedir. Ancak çevre değişkenlerinin silinmesi seyrek olarak karşımıza çıkabilecek bir durumdur. Nasıl prosesin çevre değişken bloğuna çevre değişkenleri ekleniyorsa aynı zamanda çevre değişken bloğundan bir çevre değişkeni de silinebilir. Ancak oluşturulmuş çevre değişkenlerinin değerlerinin değiştirilmesi çok karşılaşılan bir durum iken çevre değişkenlerinin silinmesi çok seyrek karşılaşılabilecek bir durumdur. Windows sistemlerinde SetenvironmentVariable fonksiyonunun ikinci parametresi NULL adres girilierek ilgili çevre dğeişkeni silinebilir. Örneğin: if (!SetEnvironmentVariable("CITY", NULL)) { fprintf(stderr, "Cannot set environment variable!..\n"); exit(EXIT_FAILURE); } Şimdi de bu konuya ilişkin örneklere bakalım: * Örnek 1, Aşağıdaki SetEnvironmentVariable API fonksiyonunun kullanımına bir örnek verilmiştir. #include #include #include #define SIZE 4096 int main(void) { char val[SIZE]; DWORD dwResult; if (!SetEnvironmentVariable("CITY", "Ankara")) { fprintf(stderr, "Cannot set environment variable!..\n"); exit(EXIT_FAILURE); } if ((dwResult = GetEnvironmentVariable("CITY", val, SIZE)) == 0 && GetLastError() == ERROR_ENVVAR_NOT_FOUND) { fprintf(stderr, "Environment variable not found!..\n"); exit(EXIT_FAILURE); } if (dwResult > SIZE) { fprintf(stderr, "Insufficient buffer!..\n"); exit(EXIT_FAILURE); } printf("%s\n", val); return 0; } * Örnek 2, Aşağıdaki Windows örneğinde önce PATH çevre değişkeni elde edilip değeri yazdırılmıştır. Sonra bu çevre değişkeni silinmiştir. Sonra yeniden elde edilmeye çalışıldığında sorun oluşacaktır. #include #include #include #define SIZE 4096 int main(void) { char val[SIZE]; DWORD dwResult; if ((dwResult = GetEnvironmentVariable("PATH", val, SIZE)) == 0 && GetLastError() == ERROR_ENVVAR_NOT_FOUND) { fprintf(stderr, "Environment variable not found!..\n"); exit(EXIT_FAILURE); } if (dwResult > SIZE) { fprintf(stderr, "Insufficient buffer!..\n"); exit(EXIT_FAILURE); } printf("%s\n", val); if (!SetEnvironmentVariable("PATH", NULL)) { fprintf(stderr, "Cannot set environment variable!..\n"); exit(EXIT_FAILURE); } if ((dwResult = GetEnvironmentVariable("PATH", val, SIZE)) == 0 && GetLastError() == ERROR_ENVVAR_NOT_FOUND) { fprintf(stderr, "Environment variable not found!..\n"); exit(EXIT_FAILURE); } if (dwResult > SIZE) { fprintf(stderr, "Insufficient buffer!..\n"); exit(EXIT_FAILURE); } printf("%s\n", val); return 0; } >>> "GetEnvironmentStrings" : Windows sistemlerinde GetEnvironmentStrings isimli API fonksiyonu çevre değişken listesini tek bir adresle aşağıdaki gibi vermektedir: Anahtar=Değer\0Anahtar=Değer\0Anahtar=Değer\0\0 Fonksiyonun prototipi şöyledir: LPCH GetEnvironmentStrings(VOID); Burada LPCH aslında char türden bir adres türünü belirtmektedir. (LPSTR türü yanlış izlenmim verebileceği gerekçesiyle kullanılmamıştır.) * Örnek 1, Aşağıdaki programda prosesin çevre değişken listesi yazıdırlmıştır. #include #include #include int main(void) { LPCH envStr; if ((envStr = GetEnvironmentStrings()) == NULL) { fprintf(stderr, "Cannot get environment strings!..\n"); exit(EXIT_FAILURE); } while (*envStr != '\0') { puts(envStr); envStr += strlen(envStr) + 1; } return 0; } >>> "FreeEnvironmentStringsA" : Prosesin tüm çevre değişken bloğunu yok etmek için Windows sistemlerinde FreeEnvironmentStrings API fonksiyonu kullanılmaktadır: BOOL FreeEnvironmentStringsA( LPCH penv ); Fonksiyon parametre olarak GetEnvirionmentStrings fonksiyonundan elde edilen adresi almaktadır. Başarı durumunda 0 dışı herhangi bir değere başarısızlık durumunda 0 değerine geri dönmektedir. Bu sistemlerde proseslerin çevre değişkenleri ile alakalı kabuk komutları ise şunlardır: >>> "set" : Windows'ta komut satırında kabuğun tüm çevre değişkenlerini görüntülemek için "set" komutu kullanılmaktadır. Örneğin, C:\>set CITY=ankara >>> "echo" : Windows'ta da yine, tıpkı UNIX/Linux sistemlerinde olduğu gibi, yazıları ekrana bastırmak için kullanılır. Fakat bu sistemlerde %isim% sentaksı uygulanır. Örneğin, C:\>echo %CITY% ankara Şimdi de bu kabuk komutlarının kullanımına ilişkin bir örnek verelim: * Örnek 1, Aşağıdaki örnekte de bir çevre değişkeninin değerinin sonuna ekeleme yaapalım: C:\>set CITY=Ankara C:\>echo %CITY% Ankara C:\>set CITY=%CITY%-Istanbul C:\>echo %CITY% Ankara-Istanbul Pekiyi prosesin çevre değişkenlerinden kim ve nasıl faydalanmaktadır? İşte çevre dğeişkenleri genel olarak kullanıcılar tarafından (ya da bazen programlar tarafından) set edilir. Birtakım fonksiyonlar ve programlar da onlara bakarak eylemleri üzerinde bazı belirlemeler yaparlar. Örneğin biz Microsoft ya da gcc derleyicilerinde derleme işlemini yaparken açısal parantezler içerisinde ya da iki tırnak içerisinde berlirtilmiş olan include dosyaları nerede bu derleyiciler tarafından nerede aranmaktadır? İşte derleyiciler bunları belli bir dizinde ararlar. Ancak komut satırı argümanlarıyla verilen dizinlere ve bazı çevre değişkenlerinin belirttiği dizinlere de bakarlar. Bu sayede biz kendi include dosyalarımızı bir dizine yerleştirebiliriz ve derleyicimizin o dizine bakmasını çevre değişkenini set ederek sağlayabiliriz. Diğer yandan aşağı seviyeli programı kullanırken o programların başvurduğu çevre değişkenlerinin neler olduğunu onların dokümanlarından öğrenebilirsiniz. Örneğin biz bir program yazacak olalım. Programımız bir "data dosyası" kullanacak olsun. Bu data dosyası default durumda programın çalıştığı dizinde "data.dat" isminde bulunabilir. Ancak programımız kullanıcının bu adat dosasının ismini ve yerini değiştirmesine izin verebilir. Bunun için biz DATA_LOCATION isminde bir çevre değişkeni uydurabiliriz. Programı kullanan kişinin bu data dosyasını yerleştirdikten sonra yol ifadesini bu çevre değişkenine yazmasını isteyebiliriz. Böylece kullanıcılar için daha esnek kullanım sunabiliriz. Örneğin: #define DEFAULT_DATA_PATH "data.dat" ... char *data_path; FILE *f; ... if ((data_path = getenv("DATA_LOCATION")) == NULL) data_path = DEFAULT_DATA_PATH; if ((f = fopen(data_path, "r+b")) == NULL) { fprintf(stderr, "cannot open data file: %s\n", data_path); exit(EXIT_FAILURE); } Buarada eğer kullanıcı DATA_LOCATION isimli eçvre değişkenini oluşturmamışsa data dosyası bulunulan dizinde "data.dat" ismiyle aranacaktır. Eğer kullanıcı bu çevre değişkenini set etmişse bu durumda bu çevre değişkeninin belirttiği yol ifadesi ile data dosyasının yeri tespit edilecektir. Çevre değişkenlerini işletim sistemi düzeyindeki global değişkenler gibi düşünebilirsiniz. Bunları birileri set edip birileri kullanmaktadır. Kursumuz ilerledikçe çeşitli eçvre değişkenlerinin kim tarafından ve nasıl kullanıldığına yönelik gerçek örneklerle karşılaşacağız. Aslında çevre değişkenleri üst prosesten alt prosese aktarılmaktadır. Program kabuk üzerinde çalıştırılıyorsa kabuğun çevre değişkenleri kabuktan çalıştırılan programlara aktaralacaktır. Başka bir terminal penceresi açtığımızda o eçvre değişkeni orada görünmeyecektir. Çünkü her terminalde aynı kabuk çalışıyor olsa bile bunlar farklı proseslerdir. (Örneğin biz sample programını birden fazla kez çalıştırabiliriz. Bunların her biri ayrı prosesler olur.) Pekiyi biz çevre değişkenlerinin her kabuk tarafından görünmesini nasıl sağlayabiliriz. İşte bu işlemler genel Windows ve UNIX/Linux (macOS'te UNIX türevi bir sistem gibidir) sistemlerinde farklı biçimlerde sağlanmaktadır. Fakat aslında gerçekleştirilen şey, o programı çalıştıran kabuk programının kendisinin çevre değişkenlerini değiştirilmesidir. Şimdi de ilgili sistemlerde bu işin nasıl yapıldığını inceleyelim: >> UNIX/Linux Sistemlerinde: UNIX/Linux sistemlerinde bir kabuk programı çalıştırılırken önce bazı "script dosyalarına" bakıp oradaki komutları çalıştırmaktadır. Kabuk programlarının çalışmaya başladığında otomatik olarak baktığı bu dosyalara İngilizce "shell start-up files" denilmektedir. Bu dosyaların neler olduğu kabuktan kabuğa ve kabuğun çalıştırma biçimlerine göre değişebilmektedir. Bix burada bash kabuğunun start-up dosyalarına değineceğiz. bash kabuğunun baktığı start-up dosyalar onun nasıl çalıştırıldığına göre değişebilmektedir. bash programı üç biçimde çalıştırılabilmektdir: -> Interactive login shell -> Interactive non-login shell -> Non-interactive shell Burada "interactive" sözcüğü komut satırlı çalışmayı belirtmektedir. Yani kullanıcı bir komut uygular komut çalıştırılır yeniden prompt'a düşülür. Buradaki "login shell" kabuk çalıştırıldığında "user name/password" sorulup sorulmayacağını belirtmektedir. Nihayet kabuk programları da tek bir komut çalıştırılarak sonlandırılabilmektedir. Bu çalışma biçimine de "non-interactive" çalıştırma biçimi denilmektedir. Örneğin biz bash programını "Ctrl+F+N" tuşlarına basarak çalıştrırsak "interactive login shell" biçiminde çalıştırmış oluruz. (Ctrl+F+N tuşlarıya açılan terminallere "sanal terminal (virtual terminal)" da denilmektedir. Eğer biz grafik arayüz içerisinde terminal açarsak buradaki bash programını "interactive non-login shell" biçiminde çalıştırmış oluruz. Bu tür terminal açmaya "sahte terminal (pseudo termimal)" de denilmektedir. Eğer bash "interactive login shell" biçiminde çalıştırılmışsa önce "/etc/profile" dosyasına başvurur. Eğer bu dosya varsa buradaki komutları çalıştırır. Sonra "~/.bash_profile", "~/.bash_login", "~/.profile" dosyalarından ilk olarak hangisini bulursa yalnızca onu çalıştırır. Nihayet biz bash programını "-c" seçenei ile çalıştırırsak bir tek komutu çalıştırıp bash programını sonlandırırız. Bu biçimdeki çalışma da "non-interactive" çalışmadır. Eğer bash "interactive non-login shell" biçiminde çalıştırılırsa bu durumda "~/.bashrc" dosyasını okuyup çalıştıurmaktadır. İŞ bu dosya bazı programlar tarafından oluşturulmuş da olabilir. Bu durumda komutları dosyanın sonuna ekleyebilirsiniz. bash programının dokğmanlarında bu konuyla ilgili bilgileri bulabilirsiniz: https://www.gnu.org/software/bash/manual/html_node/Bash-Startup-Files.html >> Windows Sistemlerinde: Windows sistemlerinde aslında masaüstü "explorer.exe" denilen bir prosestir. Dolayısıyla aslında biz masaüstünden bir program çalıştırdığımızda (terminal programı da dahil olmak üzere) bu programı bu "explorer.exe" programı çalıştırmaktadır. Çevre değişkenleri de üst prosesten alt prosese aktarıldığına göre bizim yapmamız gereken şey çevre değişkenlerini kalıcı bir biçimde "explorer.exe" prosesine yerleştirmektir. Bunu sağlamak için Windows 11'de "Ayarlar/Sistem Bilgisi/Gelişmiş Sistem Ayarları/Ortam Değişkenleri" penceresi açılır. Buradaki pencerede alt alta iki bölüm bulunmaktadır. Üstteki o anki aktif kullanıya ilişkin çevre değişkenlerini, aşağıdaki ise sistem genelindeli çevre değişkenlerini belirtmektedir. Eğer biz bir çevre dğeişkeninin belli bir kullanıcı için eklersek o çevre değişkeni yalnızca o kullanıcı ile giriş yapıldığında etkili olmaktadır. Eğer biz bir çevre değişkeninin sistem genelinde eklersek bu durumda bundan tüm prosesler yani başka kullanılar da etkilenecektir. Tabii buradaki pencerelerde çevre değişkenleri üzerinde güncellemeler yapıldığı zaman bu güncellemeler o anda çalışmakta olan prosesleri etkilemeyecektir. Çünkü çevre değişkenleri proses yaratılırken üst prosesten aktarılmaktadır. Bu tür durumlarda programlardan çıkıp onları yeniden çalıştırmalısınız. Pekiyi proses içerisinde pek çok çevre dğeişkeni görmekteyiz. Bunlar nereden gelmektedir? Aslında işletim sistemlerinde sistem boot edilirken peşi sıra bir grup proses yaratılmaktadır. Yani bir program başkasını o da başkasını çalıştırmaktadır. İşte bu sırada çeşitli prosesler yeni çevre değişkenlerini eklerler. Böylece çevre değişkenleri arta arta oluşmaktadır. Tabii en sonunda kabuk programı da yukarıda belirttiğimiz start-up dosyalardaki komutları çalıştırdığında oradan da çevre değişkenleri gelmektedir. Örneğin Linux'ta "user name/password" login isimli program tarafından sorulmaktadır. login programı girişi başarılı bir biçimde yaparsa "/etc/passwd" dosyası içerisinden elde ettiği bilgilerden hareketler HOME, USER, SHELL, PATH, LOGNAME, MAIL çevre değişkenlerini oluşturmaktadır. Kabuk programı login programı tarafından çalıştırılmaktadır. İşte bash isimli kabuk programının kendisi de birtakım çevre değişkenlerini prosese eklemektedir. > Hatırlatıcı Notlar: >> Windows sistemlerinde yol ifadesinin maksimum uzunluğu MAX_PATH sembolik sabitiyle belirlenmiştir. Ancak UNIX/Linux sistemlerinde bunun karşılığı olan PATH_MAX sembolik sabiti define edilmemiş olabilir. (Linux sistemlerinde PATH_MAX sembolik sabitinin 4096 olarak define edildiğini anımsayınız.) İşte UNIX/Linux sistemlerinde yol ifadesinin maksimum uzunluğu taşınabilir bir biçimde elde edenbilmek için aşağıdaki gibi bir fonksiyonun yazılması gerekmektedir: long path_max(void) { static long result = 0; #define PATH_MAX_INDETERMINATE_GUESS 4096 #ifdef PATH_MAX result = PATH_MAX; #else if (result == 0) { errno = 0; if ((result = pathconf("/", _PC_PATH_MAX)) == -1 && errno == 0) result = PATH_MAX_INDETERMINATE_GUESS; } #endif return result; } Aşağıdaki örnekte önce yokl ifadesi için gerekli olan alan path_max fonksiyonu ile elde edilmiş sonra da yol ifadesinin yerleştirileceği alan malloc fonksiyonuyla tahsis edilmiştir. * Örnek 1, #include #include #include #include void exit_sys(const char *msg); long path_max(void) { static long result = 0; #define PATH_MAX_INDETERMINATE_GUESS 4096 #ifdef PATH_MAX result = PATH_MAX; #else if (result == 0) { errno = 0; if ((result = pathconf("/", _PC_PATH_MAX)) == -1 && errno == 0) result = PATH_MAX_INDETERMINATE_GUESS; } #endif return result; } int main(void) { char *cwd; long size; size = path_max(); if ((cwd = (char *)malloc(size)) == NULL) exit_sys("malloc"); if (getcwd(cwd, size) == NULL) exit_sys("getcwd"); printf("size: %ld, cwd = %s\n", size, cwd); free(cwd); return 0; } void exit_sys(const char *msg) { perror(msg); exit(EXIT_FAILURE); }