Syscalls
Files
COP-3402
Table of Contents
What is systems software?
The bridge between kernel and applications
| Applications |
| Systems software |
| Kernel |
| Hardware |
Kernel design
Kernel virtualizes hardware
- Abstract away hardware difference
- Share hardware resources
- Protect hardware from users
Don't want users to arbitrarily read/write memory, hardware, etc., even if you're the only user (don't want a programming bug to disrupt entire system).
Kernel-/user-space boundary
(Diagram)
- User-space application reads a file
- Kernel-space function implement file abstraction
- Hardware stores file contents
Diagram:
- User-space calls read
- Read implemented in the kernel
- Kernel interprets read to communicate with hardware
Kernel provides library of functions to manage kernel abstractions.
User applications call kernel to interface to hardware and maintain abstractions on its behalf.
For example, calling open is a kernel library function which handles paths, the file abstraction, and interfacing with storage hardware
This is the case for so-called monolithic kernels, where kernel abstractions are largely managed in kernel space.
https://en.wikipedia.org/wiki/Tanenbaum%E2%80%93Torvalds_debate
Kernel interface: syscalls
- System calls (syscalls)
- Functions that operate on kernel abstractions
- Processes, memory, files, etc.
Implementation: protected mode
- LPI Figure 3-1
- Hardware interrupt to enter kernel space
How do we enforce kernel protections?
Learn more about kernel design in operating systems.
System Calls (syscalls)
Making syscalls
- Just like a C function
- The C library handles
- Setting up parameters
- Triggering interrupt
- Collecting return values and error states
You can add your own syscall to Linux!
What syscalls are available?
man syscalls
manpage sections
man man
- Section 2: system calls
- Section 3: library calls
Analogy:
- Going to the supermarket yourself
- Interface directly with the supermarket
- Using a delivery app
- Interface with a service that then interfaces with the supermarket
C library calls
man 3 fopen man 3 fprintf man 3 fscanf
Operates on FILE data structures, a C library abstraction. Adds additional features, like buffering, formatting, etc.
Diagram showing the difference w.r.t. to the kernel boundary.
- Back to kernel vs. user
- Call fopen, which runs in user-space; then fopen calls the kernel
- Instead of user calling kernel directly
syscalls
man 2 open man 2 write man 2 read
Operates directly on kernel files, references by file descriptors. Work with raw bytes.
Error handling
The UNIX way
- DIY error handling
- Check syscall return value
- Inspect error state
Example: open()
int fd = open(path, O_RDONLY);
if (-1 == fd) {
fprintf(stderr, "open failed with error %d\n", errno);
exit(EXIT_FAILURE);
}
Check return value for error.
Look at errno for type of error.
Documentation
man 2 open
- RETURN VALUE
- ERRORS
Error helper: perror()
int fd = open(path, O_RDONLY);
if (-1 == fd) {
// fprintf(stderr, "open() failed with error %d\n", errno);
perror("open") # perror will interpret errno for you
exit(EXIT_FAILURE);
}
Full example of errno.c
#include <fcntl.h> // O_RDONLY, open
#include <unistd.h> // read, close
#include <errno.h> // errno
#include <stdio.h> // perror, fprintf
#include <stdlib.h> // exit
int main(int argc, char **argv) {
if (argc < 2) {
fprintf(stderr, "USAGE: %s file\n", argv[0]);
exit(1);
}
char *path = argv[1];
int fd = open(path, O_RDONLY);
if (-1 == fd) {
perror("open");
exit(1);
}
if (-1 == close(fd)) {
perror("close");
}
}
strace ./errno missingfile |& egrep "^(open|exit|close|perror)" strace ./errno files.c |& egrep "^(open|exit|close|perror)"
Notice that exit and perror are not in strace, because these are C library calls. exit wraps a call to exit_group and perror is a user-space function that interprets the errno. Also note that open is implemented by calling the openat syscall, which is another variant of open.
Using file syscalls
File Status
| Symbol | Reference | Reading |
|---|---|---|
| stat() | man 2 stat |
LPI 15.1 |
| struct stat | man 3 stat |
|
| st_mode | man 7 inode |
Use the stat command to see what kind of info is stored in the stat struct.
Full example of stat.c
#include <errno.h> // errno
#include <stdio.h> // perror, fprintf
#include <stdlib.h> // exit
#include <inttypes.h> // PRIdMAX
#include <sys/stat.h> // stat
int main(int argc, char **argv) {
if (argc < 2) {
fprintf(stderr, "USAGE: %s path\n", argv[0]);
exit(1);
}
char *path = argv[1];
struct stat statbuf;
if (stat(path, &statbuf) == -1) {
perror("stat");
exit(EXIT_FAILURE);
}
printf("links: %" PRIdMAX "\n", statbuf.st_nlink);
printf("size: %" PRIdMAX "\n", statbuf.st_size);
switch (statbuf.st_mode & S_IFMT) {
case S_IFREG:
printf("REG\n");
break;
case S_IFDIR:
printf("DIR\n");
break;
case S_IFSOCK:
printf("SOCK\n");
break;
case S_IFLNK:
printf("LNK\n");
break;
case S_IFBLK:
printf("BLK\n");
break;
case S_IFCHR:
printf("CHR\n");
break;
case S_IFIFO:
printf("FIFO\n");
break;
default:
// should never happen
printf("UNKNOWN\n");
break;
}
}
If hard links to directories can't be created by the user, where are these hard links from?
From the .. parent directories and subdirectories.
Reading Files
| Symbol | Reference | Reading |
|---|---|---|
| open() | man 2 open |
LPI 4.1 |
| read() | man 2 read |
|
| close() | man 2 close |
Full example of files.c
#include <fcntl.h> // O_RDONLY, open
#include <unistd.h> // read, close
#include <errno.h> // errno
#include <stdio.h> // perror, fprintf
#include <stdlib.h> // exit
int main(int argc, char **argv) {
if (argc < 2) {
fprintf(stderr, "USAGE: %s file\n", argv[0]);
exit(1);
}
char *path = argv[1];
int fd = open(path, O_RDONLY);
if (-1 == fd) {
perror("open");
exit(1);
}
const int BUFSIZE = 10;
char buf[BUFSIZE];
ssize_t bytes = read(fd, buf, BUFSIZE);
if (-1 == bytes) {
perror("read");
exit(1);
}
for (int i = 0; i < bytes; i++) {
printf("%c", buf[i]);
}
if (-1 == close(fd)) {
perror("close");
}
}
Writing Files
| Symbol | Reference | Reading |
|---|---|---|
| open() | man 2 open |
LPI 4.1 |
| read() | man 2 read |
|
| close() | man 2 close |
Full example of filewrite.c
#include <fcntl.h> // O_RDONLY, open
#include <unistd.h> // read, write, close
#include <errno.h> // errno
#include <stdio.h> // perror, fprintf
#include <stdlib.h> // exit
#include <string.h> // strlen
int main(int argc, char **argv) {
if (argc < 3) {
fprintf(stderr, "USAGE: %s file\n", argv[0]);
exit(1);
}
char *path = argv[1];
char *msg = argv[2];
int fd = open(path, O_WRONLY | O_CREAT | O_TRUNC, 0640); // what do these do?
if (-1 == fd) {
perror("open");
exit(1);
}
/* char *msg = "hello, world!\nmy name is mud"; */
ssize_t msgc = strlen(msg);
ssize_t totalwritten = 0;
while (totalwritten < msgc) {
ssize_t writec = write(fd, msg + totalwritten, msgc - totalwritten);
fprintf(stderr, "writec: %ld\n", writec);
if (-1 == writec) {
perror("write");
exit(EXIT_FAILURE);
}
totalwritten += writec;
}
if (-1 == close(fd)) {
perror("close");
}
}
Illustrate with figure showing the string as an array and the counters/pointers into that array.
Directory Operations
| Symbol | Reference | Reading |
|---|---|---|
| opendir() | man 3 opendir |
LPI 18.8 |
| readdir() | man 3 readdir |
|
| closedir() | man 3 closedir |
Full example of dir.c
#include <fcntl.h> // O_RDONLY, open
#include <unistd.h> // read, close
#include <errno.h> // errno
#include <stdio.h> // perror, fprintf
#include <stdlib.h> // exit
#include <dirent.h> // opendir, readdir, DIR, struct dirent
int main(int argc, char **argv) {
if (argc < 2) {
fprintf(stderr, "USAGE: %s path\n", argv[0]);
exit(1);
}
char *path = argv[1];
DIR *dirp = opendir(path);
if (NULL == dirp) {
perror("opendir");
exit(EXIT_FAILURE);
}
struct dirent *curdir;
// ls -f should be the same order
// why not same order as ls?
while ((curdir = readdir(dirp)) != NULL) {
printf("%s\n", curdir->d_name);
// concatenating path names?
}
if (-1 == closedir(dirp)) {
perror("closedir");
exit(1);
}
}
This is actually a C library call that uses the underlying getdents syscall (formerly readdir). See man 2 getdents.
File links
| Symbol | Reference | Reading |
|---|---|---|
| link() | man 2 link |
LPI 18.3 |
| rename() | man 2 rename |
LPI 18.4 |
links.c
#include <errno.h> // errno
#include <stdio.h> // perror, fprintf
#include <stdlib.h> // exit
#include <unistd.h> // link
int main(int argc, char **argv) {
if (argc < 3) {
fprintf(stderr, "USAGE: %s path1 path2\n", argv[0]);
exit(1);
}
char *path1 = argv[1];
char *path2 = argv[2];
if (link(path1, path2)) {
perror("link");
exit(1);
}
}
rename.c
#include <errno.h> // errno
#include <stdio.h> // perror, fprintf
#include <stdlib.h> // exit
#include <unistd.h> // rename
int main(int argc, char **argv) {
if (argc < 3) {
fprintf(stderr, "USAGE: %s path1 path2\n", argv[0]);
exit(1);
}
char *path1 = argv[1];
char *path2 = argv[2];
if (rename(path1, path2)) {
perror("rename");
exit(1);
}
}