SYSTEMS PROGRAMMING SYSTEMS PROGRAMMING System calls is a

  • Slides: 58
Download presentation
SYSTEMS PROGRAMMING

SYSTEMS PROGRAMMING

SYSTEMS PROGRAMMING System calls is a programmer’s functional interface to the UNIX kernel. 1.

SYSTEMS PROGRAMMING System calls is a programmer’s functional interface to the UNIX kernel. 1. 2. 3. File management Process management Error handling

ERROR HANDLING: ERRNO Most system calls are capable of failing. It if happens, system

ERROR HANDLING: ERRNO Most system calls are capable of failing. It if happens, system call returns a value of -1. L-1 gives no clue why the error occurred. why Each process has a global variable errno that holds the errno numeric code of the last system-call error. (Initially, it 0). “/usr/include/sys/errno. h” #define EPERM 1 /*Operation not permitted*/ #define ENOENT 2 /*No such file or directory*/ #define EIO 5 /* I/O error */ #define EMFILE 24 /* Too many open files */ #include <errno. h>

ERROR HANDLING: PERROR() PERROR void perror(char *str) Displays str : description of the last

ERROR HANDLING: PERROR() PERROR void perror(char *str) Displays str : description of the last system call error. No error: “Error 0”. 0” G errno should be manually reset to 0 after system errno call failure!

ERROR HANDLING: PERROR() PERROR errno=2 main: No such file or directory errno=2 main: No

ERROR HANDLING: PERROR() PERROR errno=2 main: No such file or directory errno=2 main: No such file or directory main: Error 0

UNIX FILE SYSTEM Unix files are organized by hierarchy of labels commonly known as

UNIX FILE SYSTEM Unix files are organized by hierarchy of labels commonly known as directory structure Regular files: sequence of bytes that generally files corresponds to code or data. Directory files: stored in a special format and form the files backbone of the file system (directory-specific system calls). Special files correspond to peripherals, such as printers, disks, pipes and sockets. File A UNIX file is a linear sequence of bytes 0 byte

FILE DESCRIPTOR File descriptor is a non-negative integer returned by open() or creat(), it

FILE DESCRIPTOR File descriptor is a non-negative integer returned by open() or creat(), it is used in subsequent I/0 system calls on the file. File descriptors are numbered subsequently. By convention, 0 – stdin, 1 – stdout, 2 – strerr. Each descriptor has its private set of properties: File a file pointer (offset within file; is changed by read/write/lseek) flag indicating if the file descriptor 0 byte should automatically be closed if the process execs flag indicating if all of the output to the file should be appended to the end of file.

FILE MANAGEMENT SYSTEM CALLS Manipulates regular, directory and special files.

FILE MANAGEMENT SYSTEM CALLS Manipulates regular, directory and special files.

OPENING A FILE: OPEN() int open(char* file. Name, int mode[, int perm]) file. Name:

OPENING A FILE: OPEN() int open(char* file. Name, int mode[, int perm]) file. Name: absolute or relative pathname file. Name mode(|): O_RDONLY, O_WRONLY, O_RDWR mode O_APPEND: file pointer is at the end of the file before each write() O_CREAT: if file doesn’t exist, create it with process’s effective UID as owner ID. O_EXCL: if O_CREAT and file exists, then open() fails. O_NONBLOCK: pipes O_TRUNC: if file exists, it is truncated to length 0. perm: permission (octal number) of created file. perm Returns lowest unopened nonnegative file descriptor, or -1 if fails.

CREATING A FILE #include<fcntl. h> #include<stdio. h> #include<stdlib. h> void main(){ int fd =

CREATING A FILE #include<fcntl. h> #include<stdio. h> #include<stdlib. h> void main(){ int fd = open (“reverse. c”, O_CREAT | O_RDWR, 0600); if (fd == -1) { perror ("reverse: "); exit (1); } … }

OPENING AN EXISTING FILE #include<fcntl. h> #include<stdio. h> #include<stdlib. h> void main(int argc, char

OPENING AN EXISTING FILE #include<fcntl. h> #include<stdio. h> #include<stdlib. h> void main(int argc, char *argv[]){ if (argc>1) { int fd = open (argv[1], O_RDONLY); if (fd == -1) { perror ("reverse: "); exit (1); } … }}

CREATING A FILE: CREAT() int creat(char* file. Name, mode_t perm) open(file. Name, O_CREAT|O_WRONLY|O_TRUNC, perm)

CREATING A FILE: CREAT() int creat(char* file. Name, mode_t perm) open(file. Name, O_CREAT|O_WRONLY|O_TRUNC, perm) <sys/stat. h> S_IRUSR: owner read permit S_IWUSR: owner write permit S_IXUSR: owner execute permit S_IRGRP: group read permit S_IWGRP: group write permit S_IROTH: others read permit S_IWOTH: owners write permit S_IXOTH: others execute permit open(“myfile”, O_CREAT, S_IRUSR | S_IXOTH)

READING FROM A REGULAR FILE: READ() size_t read(int fd, void* buf, size_t count) Copies

READING FROM A REGULAR FILE: READ() size_t read(int fd, void* buf, size_t count) Copies up to count bytes from the file referenced by count fd into the buffer buf. fd buf Updates current file position. If successful, returns number of bytes that it read; otherwise, -1. If it was attempted after the last byte has already been read, returns 0 (EOF). Low-level input: no formatting capabilities of scanf. Faster than scanf.

 Assume, fd points to some file. int chars. Read; char buffer [4096]; while

Assume, fd points to some file. int chars. Read; char buffer [4096]; while (1) { chars. Read = read (fd, buffer, 4096); if (chars. Read == 0) break; /* EOF */ if (chars. Read == -1) { perror ("reverse: "); exit (1); } … }

WRITING TO A REGULAR FILE: WRITE() size_t write(int fd, void* buf, size_t count) Copies

WRITING TO A REGULAR FILE: WRITE() size_t write(int fd, void* buf, size_t count) Copies up to count bytes from the buffer buf to the count file referenced by fd. If O_APPEND was set for fd, file position is set to the fd end of the file before each write. Updates current file position. If successful, returns number of bytes that were written; otherwise, -1. If returned number is less than count, then the disk probably filled up. Low-level output: no formatting capabilities.

 Assume, fd and out are two file descriptors. int chars. Read, chars. Written;

Assume, fd and out are two file descriptors. int chars. Read, chars. Written; char buffer [4096]; while (1) { chars. Read = read (fd, buffer, 4096); if (chars. Read == 0) break; /* EOF */ if (chars. Read == -1) { perror ("reverse: "); exit (1); } chars. Written=write(out, buffer, chars. Read); if (chars. Written!=chars. Read){ perror ("reverse: "); exit (1); } } }

MOVING IN A FILE: LSEEK() off_t lseek(int fd, off_t offset, int mode) Changes current

MOVING IN A FILE: LSEEK() off_t lseek(int fd, off_t offset, int mode) Changes current file position in the file referenced by fd. offset: long integer offset mode describes how to interpret the offset. mode offset <stdio. h> or <unistd. h>: <unistd. h> offset is relative to the beginning of the file offset (SEEK_SET), or to the current file position (SEEK_CUR), or to the end (SEEK_END) Fails if you try to move before the start of the file. Returns current file position, or -1 if fails.

 Assume, fd is a file descriptor, lines[] is an array that holds starting

Assume, fd is a file descriptor, lines[] is an array that holds starting positions of each line in the file. for (i = 0; i <10; i--) { int chars. Read; char buffer [4096]; lseek (fd, lines[i], SEEK_SET); chars. Read = read (fd, buffer, lines[i+1] - lines[i]); write (1, buffer, chars. Read); }

MOVING IN A FILE: LSEEK() Find a current location in the file referenced by

MOVING IN A FILE: LSEEK() Find a current location in the file referenced by fd: fd lseek (fd, 0, SEEK_CUR); If you move past the end of file and perform write(), Unix automatically extends the size of the file and treats intermediate area as NULLS (0) Unix does not allocate disk area for intermediate space!! (so called “sparse” files)

CLOSING A FILE: CLOSE() int close(int fd) Closing a file descriptor releases any record

CLOSING A FILE: CLOSE() int close(int fd) Closing a file descriptor releases any record locks on the file. All open files are automatically closed by the kernel when a process terminates. Returns 0, or -1 if fails. close(fd);

DELETING A FILE: UNLINK() int unlink(const char* file. Name) Removes the hard link from

DELETING A FILE: UNLINK() int unlink(const char* file. Name) Removes the hard link from the name file. Name to its file. Name file. If it is the last link to the file, the file’s resources are deallocated. If any process’s file descriptors are currently associated with the file, the file is removed only after all of its file descriptors are closed. So executable can unlink itself during execution and still continue to completion. Returns 0 or -1, if fails. unlink(tmpfd);

OBTAINING FILE INFORMATION: STAT() int stat(const char* name, struct stat* buf) int fstat(int fd,

OBTAINING FILE INFORMATION: STAT() int stat(const char* name, struct stat* buf) int fstat(int fd, struct stat* buf)int lstat(const char* name, struct stat* buf) Fills the buffer buf with information about file name lstat() : returns information about the symbolic link itself Returns 0 or -1, if fails

STAT STRUCTURE “/usr/include/sys/stat. h” st_dev the device number st_dev st_ino the inode number st_ino

STAT STRUCTURE “/usr/include/sys/stat. h” st_dev the device number st_dev st_ino the inode number st_ino st_mode the permission flags st_mode st_uid the user ID st_uid st_gid the group ID st_gid st_size the file size st_size st_atime the last access time st_atime st_mtime the last modification time st_mtime st_ctime the last status change time st_ctime

MACROS IN /USR/INCLUDE/SYS/STAT. H S_ISDIR(st_mode) true if directory S_ISDIR(st_mode) S_ISCHR(st_mode) true if file is

MACROS IN /USR/INCLUDE/SYS/STAT. H S_ISDIR(st_mode) true if directory S_ISDIR(st_mode) S_ISCHR(st_mode) true if file is a character S_ISCHR(st_mode) special device S_ISBLK(st_mode) true if file is a block special S_ISBLK(st_mode) device S_ISREG(st_mode) true if a regular file S_ISFIFO(st_mode) true if a pipe S_ISFIFO(st_mode) The time fields may be decoded with the standard C library asctime() and localtime() subroutines.

READING DIRECTORY INFORMATION: GETDENTS() int getdents(int fd, struct dirent* buf, int struct. Size) Reads

READING DIRECTORY INFORMATION: GETDENTS() int getdents(int fd, struct dirent* buf, int struct. Size) Reads the directory file fd from its current position and fills fd structure buf with the next entry. buf <dirent. h>: struct dirent { long d_ino; /* inode number */ off_t d_off; /* offset to next dirent */ unsigned short d_reclen; /*length of the dirent */ char d_name [NAME_MAX+1]; /*filename+’’*/ } Returns the length of directory if successful, 0 if the last entry has already been read, and -1 if fails

DIRECTORY MANIPULATION: OPENDIR() DIR * opendir (const char *dirname) Opens and returns a directory

DIRECTORY MANIPULATION: OPENDIR() DIR * opendir (const char *dirname) Opens and returns a directory stream of directory dirname, dirname or NULL, if fails. struct dirent: dirent information about directory entries. char d_name[] is the file name+’’. ino_t d_fileno is the file serial number. unsigned char d_namlen is the length of the file name. unsigned char d_type is the type of the file: DT_UNKNOWN, DT_REG (regular file), DT_DIR (directory), DT_FIFO (named pipe), DT_SOCK (localdomain socket), DT_CHR (character device), DT_BLK (block device)

DIRECTORY MANIPULATION: READDIR() AND CLOSEDIR() struct dirent * readdir (DIR *dirstream) Reads the next

DIRECTORY MANIPULATION: READDIR() AND CLOSEDIR() struct dirent * readdir (DIR *dirstream) Reads the next entry from the directory. Returns a pointer to a structure containing information about the file. This structure is statically allocated and can be rewritten by a subsequent call. If there are no more entries or an error is detected, returns a null pointer. int closedir (DIR *dirstream) Closes the directory stream dirstream Returns 0 or -1 if fails.

#include <stddef. h> #include <stdio. h> #include <sys/types. h> #include <dirent. h> int main

#include <stddef. h> #include <stdio. h> #include <sys/types. h> #include <dirent. h> int main (void){ DIR *dp= opendir (". /"); struct dirent *ep; if (dp != NULL){ while (ep = readdir (dp)) puts (ep->d_name); (void) closedir (dp); } else perror ("Couldn't open"); return 0; }

CHANGING FILE’S PERMISSIONS: CHMOD() int chmod (const char* file. Name, int mode) int fchmod

CHANGING FILE’S PERMISSIONS: CHMOD() int chmod (const char* file. Name, int mode) int fchmod (int fd, int mode) Changes the mode of file. Name to mode (specified as octal) Set user ID and set group ID flags have values 4000 and 2000, respectively. int main() { int flag; flag = chmod("test. txt", 0600); if (flag == -1) perror("problem setting mode"); }

PROCESS MANAGEMENT Process is a unique instance of a running or a runnable program.

PROCESS MANAGEMENT Process is a unique instance of a running or a runnable program. Every process in UNIX has code data a stack 1. Parameters 2. the address of the calling programs 3. the return address, e. g. where to go when the function returns 4. The automatic (local) variables a unique process ID (PID)

INIT PROCESS When UNIX starts (boots), there is only one process, called init, with

INIT PROCESS When UNIX starts (boots), there is only one process, called init, with PID = 1. init The only way to create a new process is to duplicate an existing process. So init is the ancestor of all subsequent processes. Initially, init duplicates several times and each child process replaces its code with the code of the executable getty which is responsible for getty user logins. init PID =1 Child getty PID=4 Child getty PID =5

PARENT AND CHILD PROCESSES It is common for a parent process to suspend itself

PARENT AND CHILD PROCESSES It is common for a parent process to suspend itself until one of its child processes terminates. For example, execution in the foreground: Parent process PID 34 running shell Child process PID=35 running shell PID 34 running shell, shell waiting for a child exec() wait() PID 34 running shell, shell awakens fork() PID=35 running utility signal exit() PID=35 terminates

CREATING A NEW PROCESS: FORK() pit_t fork(void) Causes a process to duplicate The child

CREATING A NEW PROCESS: FORK() pit_t fork(void) Causes a process to duplicate The child process inherits a copy of its parent's code, data, stack, open file descriptors, and signal tables The only difference is in the PID and parent process ID (PPID) If fork() succeeds, it returns the PID of the child to the parent process and 0 to the child process. If fork() fails, it returns a -1 to the parent process and the child process is not created.

GETTING PID AND PPID: GETPID() AND GETPPID() pid_t getpid(void) Returns the PID pid_t getppid(void)

GETTING PID AND PPID: GETPID() AND GETPPID() pid_t getpid(void) Returns the PID pid_t getppid(void) Returns the PPID They always succeed The PPID value for process with PID=1 is 1

ORPHAN PROCESS: PPID BECOMES 1

ORPHAN PROCESS: PPID BECOMES 1

TERMINATING A PROCESS: EXIT() void exit(int status) Closes all of a process' file descriptors,

TERMINATING A PROCESS: EXIT() void exit(int status) Closes all of a process' file descriptors, deallocates its code, data, and stack; then terminates the process. When a child process terminates, it sends its parent a SIGCHLD signal and waits for its termination code (status) to be accepted. Only lower 8 bits of status are used; so value is 0 -255. A process which is waiting for its parent process to accept its return code is called a zombie process. zombie A parent accepts a child's termination code by executing wait(). The kernel ensures that all children of terminating process are adopted by init always accepts its children termination codes.

EXAMPLE

EXAMPLE

ZOMBIE PROCESSES A process cannot leave the system until parent process accepts its termination

ZOMBIE PROCESSES A process cannot leave the system until parent process accepts its termination code If parent process is dead; init adopts process and accepts code If the parent process is alive but is unwilling to accept the child's termination code (never executes wait()), the child process will remain a zombie process. Zombie processes do not take up system resources: No data, code, stack But use an entry in the system's fixed-size process table

WAITING FOR A CHILD: WAIT() pid_t wait(int *status) Causes a process to suspend until

WAITING FOR A CHILD: WAIT() pid_t wait(int *status) Causes a process to suspend until one of its child processes terminates. A successful call returns the PID of the terminated child process, places its status code into status: If the rightmost byte is zero, the leftmost byte contains the low 8 bits of the value returned by the child's exit() or return() call Otherwise, the rightmost 7 bits are Signal Number that caused the child to terminate and the last bit is 1 if the child core is dumped. No children: wait() returns immediately with -1 Has zombies: wait() returns immediately with the status of one of the zombies.

DIFFERENTIATING A PROCESS: EXEC FAMILY With the exec family, a process replaces its current

DIFFERENTIATING A PROCESS: EXEC FAMILY With the exec family, a process replaces its current code data stack PID and PPID stay the same Only the code that the process is executing changes

DIFFERENTIATING A PROCESS: EXEC FAMILY Use the absolute or relative name of executable: int

DIFFERENTIATING A PROCESS: EXEC FAMILY Use the absolute or relative name of executable: int execl(const char* path, const char* arg 0, . . . , const char* argn, NULL) int execv(const char* path, const char* argv[ ]) Use $PATH variable to find the executable: int execlp(const char* path, const char* arg 0, . . . , const char* argn, NULL) int execvp(const char* path, const char* argv[ ]) Replaces the calling process’ code, data and stack by those of the executable whose pathname is in path

DIFFERENTIATING A PROCESS: EXEC FAMILY execl() and execlp(): execl() execlp() invoke executable with string

DIFFERENTIATING A PROCESS: EXEC FAMILY execl() and execlp(): execl() execlp() invoke executable with string arguments pointed by arg 1, . . arg 1 argn arg 0 should be the name of the executable itself arg 0 the list of arguments should terminate with NULL. execv() and execvp(): execv() invoke executable with string arguments pointed by argv[1], . . argv[n] argv[n+1] is NULL argv[n+1] argv[0] should be the name of the executable itself. argv[0] If executable is not found, -1 is returned Otherwise the calling process replaces its code, data, stack with executable’s, starts executing the new code

EXAMPLE

EXAMPLE

CHANGING DIRECTORIES: CHDIR() Every process has a current working directory which is used when

CHANGING DIRECTORIES: CHDIR() Every process has a current working directory which is used when processing a relative pathname A child process inherits the current working directory from its parent int chdir(const char* pathname) Sets a process' current working directory to pathname The process must have execute permission from the directory to succeed Returns -1 if fails Otherwise, 0

EXAMPLE

EXAMPLE