xv6 book Chapter 1 Operating system interfaces

  • The job of an operating system is to share a computer among multiple programs and to provide a more useful set of services than the hardware alone supports.
  • An operating system provides services to user programs through an interface.

fork() creates a new process, called the child process, with exactly the same memory contents as the calling process, called the parent process. fork() returns in both the parent and the child. In the parent, fork() returns the child’s PID; in the child, fork() return zero.

exit() system call causes the calling process to stop executing and to release resources such as memory and open files. exit() takes an integer argument, conventionally 0 to indicate success and 1 to indicate failure.

wait() system call returns the PID of an exited (or killed) child of the current process and copied the exit status of the child to the address passed to wait.

  • If none of the caller’s children has exited, wait() waits for one to do so.
  • If the caller has no children, wait() immediately returns -1.
  • If the parent doesn’t care about the exit status of a child, it can pass a 0 address to wait().
int pid = fork();
if (pid > 0) {
    
    
	printf("parent: child=%d\n", pid);
	pid = wait((int *) 0);
	printf("child %d is done\n", pid);
}
else if (pif == 0) {
    
    
	printf("child: exiting\n");
	exit(0);
}
else {
    
    
	printf("fork error\n");
}

exec() system call replaces the calling process’s memory with a new memory image loaded from a file stored in the file system. When exec succeeds, it does not return to the calling program; instead, the instructions loaded from the file start executing at the entry point declared in the ELF header.

char *argv[3];
/*
 * most programs ignore the first element of the argument array, which is
 * conventionally the name of the program
 */
argv[0] = "echo"; 
argv[1] = "hello";
argv[2] = 0;
exec("/bin/echo", argv);
printf("exec error\n");

read(fd, buf, n) reads at most n bytes from the file descriptor fd, copies them into buf, and returns the number of bytes read. Each file descriptor that refers to a file has an offset associated with it. read() reads data from the current file offset and then advances that offset by the number of bytes read: a subsequent read will return the bytes following the ones returned by the first read. When there are no more bytes to read, read() return zero to indicate the end of the file.

write(fd, buf, n) writes n bytes from buf to the file descriptor fd and returns the number of bytes written. Fewer than n bytes are written only when an error occurs. Like read(), write writes data at the current file offset and then advances that offset by the number of bytes written: each write picks up where the previous one left off.

char buf[512];
int n;

for ( ; ; ) {
    
    
	n = read(0, buf, sizeof buf);
	if (n == 0) {
    
    
		break;
	}
	if (n < 0) {
    
    
		fprintf(2, "read error\n");
		exit(1);
	}
	if (write(1, buf, n) != n) {
    
    
		fprintf(2, "write error\n");
		exit(1);
	}
}

close() system call releases a file descriptor, making it free for reuse by a future open(), pipe(), or dup() system call. A newly allocated file descriptor is always the lowest numbered unused descriptor of the current process.

char *argvc[2];

argv[0] = "cat";
argv[1] = 0;
if (fork() == 0) {
    
    
	close(0);
	
	open("input.txt", O_RDONLY);
	exec("cat", argv);
}

dup() system call duplicates an existing file descriptor, returning a new one that refers to the same underlying I/O object.

fd = dup(1);
// Both file descriptors share an offset,
// just as the file descriptors duplicated by fork() do.
write(1, "hello ", 6);
write(fd, "wordl\n", 6);

pipe() creates a new pipe and records the read and write file descriptors in the array p. If no data is available, a read on a pipe waits for either data to be written on for all file descriptors referring to the write end to be closed; in the latter case, read will return 0, just as if the end of a data file had been reached.

int p[2];
char *argv[2];

argv[0] = "wc";
argv[1] = 0;

pipe(p);
if (fork() == 0) {
    
    
	close(0);
	// p[0] the read end of the pipe
	dup(p[0]);
	close(p[0]);
	close(p[1]);
	exec("/bin/wc", argv);
}
else {
    
    
	close(p[0]);
	write(p[1], "hello world\n", 12);
	close(p[1]);
}	

chdir() system call can change the calling process’s current directory.

chdir("/a");
chdir("b");
open("c", O_RDONLY);
// both these two fragments open the same file, but the first change the
// process's directory to /a/b; the second neither refers to nor changes
// the process's current directory
open("/a/b/c", O_RDONLY);

mkdir() creates a new directory

open() with the O_CREATE flag creates a new data file

mknod() creates a special file that refers to a device. Associated with a device file are the major and minor device numbers (the two arguments to mknod), which uniquely identify a kernel device. When a process later opens a device file, the kernel diverts read() and write() system calls to the kernel device implementation instead of passing them to the file system.

mkdir("/dir");
fd = open("/dir/file", O_CREATE|O_WRONLY);
close(fd);
mknod("/console", 1, 1);

link() system call creates another file system name referring to the same inode as an existing file

open("a", O_CREATE|O_WRONLY);
// reading from or writing to a is the same as reading from or writing to b
link("a", "b");

unlink() system call removes a name from the file system. The file’s inode and the disk space holding its content are only freed when the file’s link count is zero and no file descriptors refer to it.

unlink("a");

猜你喜欢

转载自blog.csdn.net/weixin_41714373/article/details/109722603