The vi editor - Georgia State University
Download
Report
Transcript The vi editor - Georgia State University
SYSTEMS PROGRAMMING
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 call returns a value of -1.
-1 gives no clue why the error occurred.
Each process has a global variable errno that
holds the 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()
void
perror(char *str)
Displays str : description of the last system
call error.
No error: “Error 0”.
errno should be manually reset to 0 after
system call failure!
ERROR HANDLING: 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 directory structure.
Regular files: sequence of bytes that generally
corresponds to code or data.
Directory files: stored in a special format and
form the backbone of the file system (directoryspecific system calls).
Special files correspond to peripherals, such as
printers, disks, pipes and sockets.
File
0 byte
A
UNIX file is a linear
sequence of bytes.
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:
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
FILE MANAGEMENT SYSTEM CALLS
Manipulates
regular, directory and special files.
OPENING A FILE: OPEN()
int
open(char* fileName, int mode[, int perm])
fileName: absolute or relative pathname
mode(|): O_RDONLY, O_WRONLY, O_RDWR
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 .
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 = 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 *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* fileName, mode_t perm)
open(fileName,
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 up to count bytes from the file
referenced by fd into the buffer 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 charsRead;
char buffer [4096];
while (1)
{
charsRead = read (fd, buffer, 4096);
if (charsRead == 0) break; /* EOF */
if (charsRead == -1) {
perror ("reverse: ");
exit (1);
}
…
}
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 file referenced by fd.
If O_APPEND was set for fd, file position is set
to the 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 charsRead, charsWritten;
char buffer [4096];
while (1)
{ charsRead = read (fd, buffer, 4096);
if (charsRead == 0) break; /* EOF */
if (charsRead == -1) {
perror ("reverse: "); exit (1);}
charsWritten=write(out,buffer,charsRead);
if (charsWritten!=charsRead){
perror ("reverse: "); exit (1);}
}
}
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
mode describes how to interpret the offset.
<stdio.h> or <unistd.h>:
offset is relative to the beginning of the file
(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 positions of each line in the
file.
for (i = 0; i <10; i--)
{ int charsRead;
char buffer [4096];
lseek (fd, lines[i], SEEK_SET);
charsRead = read (fd, buffer,
lines[i+1] - lines[i]);
write (1, buffer, charsRead);
}
MOVING IN A FILE: LSEEK()
Find
a current location in the file referenced
by 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
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* fileName)
Removes the hard link from the name fileName
to its 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, 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_ino the inode number
st_mode the permission flags
st_uid the user ID
st_gid the group ID
st_size the file size
st_atime the last access time
st_mtime the last modification time
st_ctime the last status change time
MACROS IN /USR/INCLUDE/SYS/STAT.H
S_ISDIR(st_mode)
true if directory
S_ISCHR(st_mode) true if file is a
character special device
S_ISBLK(st_mode) true if file is a block
special device
S_ISREG(st_mode) true if a regular file
S_ISFIFO(st_mode) true if a pipe
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
structSize)
Reads the directory file fd from its current position
and fills structure buf with the next entry.
<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+’\0’*/ }
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 stream of directory
dirname, or NULL, if fails.
struct dirent: information about directory entries.
char d_name[] is the file name+’\0’.
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
(local-domain socket), DT_CHR (character device),
DT_BLK (block device)
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 (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* fileName, int mode)
int fchmod (int fd, int mode)
Changes the mode of fileName 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.
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 PID = 1.
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 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 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,
waiting for a child
exec()
wait()
PID 34 running 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 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)
Returns the PPID
They always succeed
The PPID value for process with PID=1 is 1
ORPHAN PROCESS: PPID BECOMES 1
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.
A parent accepts a child's termination code by executing
wait().
The kernel ensures that all children of terminating
process are adopted by init
init always accepts its children termination codes.
EXAMPLE
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 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 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 execl(const char* path, const char*
arg0, ...,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*
arg0, ...,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():
invoke executable with string arguments pointed by
arg1,.. argn.
arg0 should be the name of the executable itself
the list of arguments should terminate with NULL.
execv() and execvp():
invoke executable with string arguments pointed by
argv[1],.. argv[n]
argv[n+1] is NULL
argv[0] should be the name of the executable itself.
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
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