Föreläsning 2 2022-04-26 DT513G Operativsystem för civilingenjörer, 4,5 högskolepoäng ------------------------------------------------------------ (Det här är bara mina egna minnesanteckningar. Även om de visar ungefär vad jag tar upp på föreläsningen, så är de kanske inte särskilt begripliga, och de ska förstås inte ses som en ersättning för föreläsningen, boken eller något annat.) Idag: Linux-grunder, systemanrop, API, Linux-API:et open read write close chdir fork exec wait pipe mmap (kap 2.3 med mera i boken) strace fopen, open 1. Linux-grunder ---------------- Raspbian, emulerad i qemu Ubuntu eller Debian i VirtualBox skal: bash med flera filträdet: filer, kataloger, /dev, /proc omdirigering pipes ett enklare skal: skalman ls > mina-filer ls | more locate luffartest.c | xargs ls -alFtr 2. Systemanrop och API ---------------------- System Calls: * Programming interface to the services provided by the OS * Typically written in a high-level language (C or C++) * Mostly accessed by programs via a high-level Application Program Interface (API) rather than direct system call use. Example: C stdio fopen and printf <--> Unix open and write * Three most common APIs are Win32 API for Windows, POSIX API for POSIX-based systems (including virtually all versions of UNIX, Linux, and Mac OS X), and Java API for the Java virtual machine (JVM) * Why use APIs rather than system calls? Example of a Standard API: Windows! "Win32 API" BOOL ReadFile(HANDLE file, LPVOID buffer, DWORD bytesToRead, LPDWORD bytesRead, LPOVERLAPPED ovl); * A description of the parameters passed to ReadFile(): - HANDLE file - the file to be read - LPVOID buffer - a buffer where the data will be read into and written from - DWORD bytesToRead - the number of bytes to be read into the buffer - LPDWORD bytesRead - the number of bytes read during the last read - LPOVERLAPPED ovl - indicates if overlapped I/O is being used System Call Implementation: * Typically, a number associated with each system call * The system-call interface maintains a table indexed according to these numbers * The system call interface invokes intended system call in OS kernel and returns status of the system call and any return values * The caller need know nothing about how the system call is implemented. Just needs to obey API and understand what OS will do as a result call. Most details of OS interface hidden from programmer by API, managed by run-time support library (set of functions built into libraries included with compiler). (bilden från CS377 Lecture 03 page 8) 3. Filer i Linux-API:et: open, read... -------------------------------------- :::::::::::::: my-c-cat-1.c :::::::::::::: // Print the contents of the file "blaj.txt" #include int main(void) { FILE *fp = fopen("blaj.txt", "r"); int c; while ((c = getc(fp)) != EOF) putchar(c); fclose(fp); } :::::::::::::: my-c-cat-2.c :::::::::::::: // Print the contents of the file "blaj.txt" #include #include #include #include int main(void) { FILE *fp = fopen("blaj.txt", "r"); if (fp == NULL) { fprintf(stderr, "Error: Couldn't open file 'blaj.txt'. " "Error %d = %s\n", errno, strerror(errno)); exit(EXIT_FAILURE); } int c; while ((c = getc(fp)) != EOF) putchar(c); fclose(fp); } :::::::::::::: my-linux-cat-1.c :::::::::::::: // Print the contents of the file "blaj.txt" #include #include int main(void) { int fd = open("blaj.txt", O_RDONLY); char c; int n; while ((n = read(fd, &c, 1)) > 0) write(1, &c, n); close(fd); return 0; } :::::::::::::: my-linux-cat-2.c :::::::::::::: // Print the contents of the file "blaj.txt" #include #include int main(void) { int fd = open("blaj.txt", O_RDONLY); char c; while (read(fd, &c, 1) == 1) write(1, &c, 1); close(fd); return 0; } :::::::::::::: my-linux-cat-3.c :::::::::::::: // Print the contents of the file "blaj.txt" #include #include #include #include #include #include int main(void) { int fd = open("blaj.txt", O_RDONLY); if (fd == -1) { fprintf(stderr, "Error: Couldn't open file 'blaj.txt'. " " Error %d = %s\n", errno, strerror(errno)); exit(EXIT_FAILURE); } char buffer[1024*1024]; int n; while ((n = read(fd, buffer, 1024*1024)) > 0) write(1, buffer, n); close(fd); return 0; } strace ./my-linux-cat-2 ... openat(AT_FDCWD, "blaj.txt", O_RDONLY) = 3 read(3, "Blajfilen!\n\nHej hej!\n", 1048576) = 21 write(1, "Blajfilen!\n\nHej hej!\n", 21) = 21 Blajfilen! Hej hej! read(3, "", 1048576) = 0 close(3) = 0 exit_group(0) = ? +++ exited with 0 +++ Avvikelser: openat och exit_group Dessutom en massa andra anrop: execve("./my-linux-cat-2", ["./my-linux-cat-2"], 0x7ffd72bd74d0 /* 47 vars */) = 0 ... openat(AT_FDCWD, "/lib/x86_64-linux-gnu/libc.so.6", O_RDONLY|O_CLOEXEC) = 3 read(3, "\177ELF\2\1\1\3\0\0\0\0\0\0\0\0\3\0>\0\1\0\0\0\260\34\2\0\0\0\0\0"..., 832) = 832 fstat(3, {st_mode=S_IFREG|0755, st_size=2030544, ...}) = 0 mmap(NULL, 8192, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7f61d2934000 mmap(NULL, 4131552, PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_DENYWRITE, 3, 0) = 0x7f61d2337000 mprotect(0x7f61d251e000, 2097152, PROT_NONE) = 0 ... strace ./my-c-cat-1 ... openat(AT_FDCWD, "blaj.txt", O_RDONLY) = 3 fstat(3, {st_mode=S_IFREG|0664, st_size=21, ...}) = 0 read(3, "Blajfilen!\n\nHej hej!\n", 4096) = 21 fstat(1, {st_mode=S_IFCHR|0620, st_rdev=makedev(136, 16), ...}) = 0 write(1, "Blajfilen!\n", 11Blajfilen!) = 11 write(1, "\n", 1) = 1 write(1, "Hej hej!\n", 9) = 9 Hej hej! read(3, "", 4096) = 0 close(3) = 0 exit_group(0) = ? Avvikelser: openat och exit_group Intressant: stdout är radbuffrad om den skriver till terminalen 4. Linux-API:et --------------- I kärnans kod: SYSCALL_DEFINE3(open, const char __user *, filename, int, flags, umode_t, mode) { if (force_o_largefile()) flags |= O_LARGEFILE; return do_sys_open(AT_FDCWD, filename, flags, mode); } #define SYSCALL_DEFINE3(name, ...) SYSCALL_DEFINEx(3, _##name, __VA_ARGS__) #define SYSCALL_DEFINEx(x, sname, ...) \ SYSCALL_METADATA(sname, x, __VA_ARGS__) \ __SYSCALL_DEFINEx(x, sname, __VA_ARGS__) #define __SYSCALL_DEFINEx(x, name, ...) \ __diag_push(); \ __diag_ignore(GCC, 8, "-Wattribute-alias", \ "Type aliasing is used to sanitize syscall arguments");\ asmlinkage long sys##name(__MAP(x,__SC_DECL,__VA_ARGS__)) \ __attribute__((alias(__stringify(__se_sys##name)))); \ ALLOW_ERROR_INJECTION(sys##name, ERRNO); \ static inline long __do_sys##name(__MAP(x,__SC_DECL,__VA_ARGS__));\ asmlinkage long __se_sys##name(__MAP(x,__SC_LONG,__VA_ARGS__)); \ asmlinkage long __se_sys##name(__MAP(x,__SC_LONG,__VA_ARGS__)) \ { \ long ret = __do_sys##name(__MAP(x,__SC_CAST,__VA_ARGS__));\ __MAP(x,__SC_TEST,__VA_ARGS__); \ __PROTECT(x, ret,__MAP(x,__SC_ARGS,__VA_ARGS__)); \ return ret; \ } \ __diag_pop(); \ static inline long __do_sys##name(__MAP(x,__SC_DECL,__VA_ARGS__)) long do_sys_open(int dfd, const char __user *filename, int flags, umode_t mode) { struct open_flags op; int fd = build_open_flags(flags, mode, &op); struct filename *tmp; if (fd) return fd; tmp = getname(filename); if (IS_ERR(tmp)) return PTR_ERR(tmp); fd = get_unused_fd_flags(flags); if (fd >= 0) { struct file *f = do_filp_open(dfd, tmp, &op); if (IS_ERR(f)) { put_unused_fd(fd); fd = PTR_ERR(f); } else { fsnotify_open(f); fd_install(fd, f); } } putname(tmp); return fd; } struct filename * getname(const char __user * filename) { return getname_flags(filename, 0, NULL); } /* In order to reduce some races, while at the same time doing additional * checking and hopefully speeding things up, we copy filenames to the * kernel data space before using them.. * * POSIX.1 2.4: an empty pathname is invalid (ENOENT). * PATH_MAX includes the nul terminator --RR. */ #define EMBEDDED_NAME_MAX (PATH_MAX - offsetof(struct filename, iname)) struct filename * getname_flags(const char __user *filename, int flags, int *empty) { struct filename *result; char *kname; int len; result = audit_reusename(filename); if (result) return result; result = __getname(); if (unlikely(!result)) return ERR_PTR(-ENOMEM); /* * First, try to embed the struct filename inside the names_cache * allocation */ kname = (char *)result->iname; result->name = kname; len = strncpy_from_user(kname, filename, EMBEDDED_NAME_MAX); if (unlikely(len < 0)) { __putname(result); return ERR_PTR(len); } /* * Uh-oh. We have a name that's approaching PATH_MAX. Allocate a * separate struct filename so we can dedicate the entire * names_cache allocation for the pathname, and re-do the copy from * userland. */ if (unlikely(len == EMBEDDED_NAME_MAX)) { const size_t size = offsetof(struct filename, iname[1]); kname = (char *)result; /* * size is chosen that way we to guarantee that * result->iname[0] is within the same object and that * kname can't be equal to result->iname, no matter what. */ result = kzalloc(size, GFP_KERNEL); if (unlikely(!result)) { __putname(kname); return ERR_PTR(-ENOMEM); } result->name = kname; len = strncpy_from_user(kname, filename, PATH_MAX); if (unlikely(len < 0)) { __putname(kname); kfree(result); return ERR_PTR(len); } if (unlikely(len == PATH_MAX)) { __putname(kname); kfree(result); return ERR_PTR(-ENAMETOOLONG); } } result->refcnt = 1; /* The empty path is special. */ if (unlikely(!len)) { if (empty) *empty = 1; if (!(flags & LOOKUP_EMPTY)) { putname(result); return ERR_PTR(-ENOENT); } } result->uptr = filename; result->aname = NULL; audit_getname(result); return result; } 5. Processer: fork, exec... --------------------------- // forktest.c #include #include #include #include #include #include #include int main(void) { printf("Jag är process %d. Min förälder är process %d\n", getpid(), getppid()); printf("Ange ett kommando: "); char command[1000]; gets(command); // Don't do this in a real program! int childpid = fork(); printf("Efter fork. Jag är process %d. Min förälder är process %d\n", getpid(), getppid()); if (childpid == 0) { // Barnet sleep(1); printf("Barnet: execl...\n"); execl(command, command, (char*)NULL); printf("Kunde inte starta programmet '%s'! Felkod %d (%s)\n", command, errno, strerror(errno)); exit(1); } else { // Föräldern int waitstatus; printf("Föräldern väntar...\n"); waitpid(childpid, &waitstatus, 0); printf("Föräldern klar!\n"); } } 6. Kommunikation: pipe, mmap ---------------------------- :::::::::::::: my-mmap-cat.c :::::::::::::: // Print the contents of the file "blaj.txt" #include #include #include #include #include #include #include #include #include int main(void) { int fd = open("blaj.txt", O_RDONLY); if (fd == -1) { fprintf(stderr, "Error: Couldn't open file 'blaj.txt'. " " Error %d = %s\n", errno, strerror(errno)); exit(EXIT_FAILURE); } struct stat statbuf; if (fstat(fd, &statbuf) == -1) { fprintf(stderr, "Error: Couldn't find length of file 'blaj.txt'. " " Error %d = %s\n", errno, strerror(errno)); exit(EXIT_FAILURE); } size_t file_length = statbuf.st_size; void *address = mmap(NULL, file_length, PROT_READ, MAP_PRIVATE, fd, 0); if (address == MAP_FAILED) { fprintf(stderr, "Error: Couldn't map file 'blaj.txt'. " " Error %d = %s\n", errno, strerror(errno)); exit(EXIT_FAILURE); } close(fd); for (size_t i = 0; i < file_length; ++i) { putchar(((char*)address)[i]); } return 0; } 7. skalman och skalman-skeleton.c ---------------------------------