cpp-cheat/posix/main.c
2017-07-20 08:40:24 +01:00

2340 lines
69 KiB
C
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/*
Cheat on the POSIX C API.
The POSIX C API is mostly an extension of the ANSI C API.
ANSI C features shall not be discussed here.
# Implementations
On most Linux systems as of 2013, the POSIX C API is implemented by the GNU C library:
<http://www.gnu.org/software/libc/>.
The GNU documentation states that POSIX compliance
is a design goal of the GNU C library.
# windows
TODO Is there a Windows implementation for the POSIX C API? Official?
*/
#include "common.h"
int main(void) {
/*
# Namespace
POSIX adds many further per header reserved names which it would be wise to follow even on ANSI C:
http://pubs.opengroup.org/onlinepubs/9699919799/functions/V2_chap02.html> section "The Name Space"
*/
/*
# errors
Typical error dealing conventions POSIX are:
- if the return value is not needed, functions return 0 on successs and either -1 on failure
or an integer which indicates failure cause
- if the return value is strictly positive, return -1 on error
- if the return value is a pointer, return `NULL` on error
- if the return value can be any integer (`ptrace` for example), return `-1`, but force the user to
clear `errno` before making the call, and check if `errno != -` after the call.
Whenever there is an error, set `errno` accordingly to determine what was the cause of the erro
# errno.h
Is defined by ANSI C, but more predefined error constants are added extended in POSIX,
Only differences from ANSI C shall be commented here. Note that errno,
perror and strerror for example are all in ANSI C.
Some of the POSIX specific errors and their error messages are:
- `EPERM`: Operation not permitted
When users try to do something which requires previledges that they don't have,
such as being the root user.
- `ENOENT`: No such file or directory
- `EINTR`: Interrupted system call
- `EIO`: I/O Error
- `EBUSY`: Device or resource busy
- `EEXIST`: File exists
- `EINVAL`: Invalid argument
- `EMFILE`: Too many open files
- `ENODEV`: No such device
- `EISDIR`: Is a directory
- `ENOTDIR`: Isnt a directory
Functions that modify errno document that. The convention is that only functions which fail modify
errno, not those that succeed.
errno can be modified as `errno = 0` for example.
*/
{
/*
# errno
`errno` can be modified by functions to contain a description of certain
standard errors.
`errno` is a lvalue and users can explicitly modify it.
This is necessary in certain error checking situations, when the error can only be decided
by changes in errno and not by the return value.
`0` indicates no `error` (ANSI C).
Since many function may change errno, you must use the functions that
depend on errno immediatelly after the function that generates the error.
*/
{
char *dirname = "i_dont_exist";
/* Assure that dirname does not exist. */
if(access(dirname, F_OK) == 0)
assert(rmdir(dirname) != -1);
errno = 0;
rmdir(dirname);
assert(errno != 0);
/* Successful calls do *not* set errno to 0. */
mkdir(dirname, 0777);
rmdir(dirname);
assert(errno != 0);
}
}
/*
# printf
This discusses `printf` and `sprintf` POSIX extensions to ANSI.
*/
{
/*
# dollar
`%2$d` means: use second argument. Treat the following arguments as if this one did not exist.
Has been incorporated in POSIX, but may break ANCI C code! (unlikely).
For that reason, compiling this generates warnings on gcc, and you should avoid this feature as:
- it is unlikely to be incorporated in ANSI C since it is a breaking change
- if you ever decide to increase portability to ANSI C
(in case some other key functions you were using someday get ANSI C alternatives),
you will have to correct this
*/
{
/*
char buf[256];
sprintf(buf, "%2$d %d %d", 0, 1);
assert(strcmp(buf, "1 0 1") == 0);
*/
}
}
/*
# math.h
The `M_PI` constants are defined by POSIX inside of `math.h`.
*/
{
/* # constants */
{
/* ANSI C way of calculating some constants. */
const float PI = acos(-1);
const float E = exp(1);
/* POSIX provides macros that expand to those constants. */
assert(fabs(M_E - E ) < 1e-6);
assert(fabs(M_PI - PI ) < 1e-6);
assert(fabs(M_PI/2.0 - M_PI_2) < 1e-6);
assert(fabs(M_PI/4.0 - M_PI_4) < 1e-6);
}
/*
# bessel
As of POSIX 7, the only major function addition to the math library
seems to be Bessel functions.
TODO understand, specially why is it so important to be here?
http://en.wikipedia.org/wiki/Bessel_function
*/
{
/*
double j0(double);
double j1(double);
double jn(int, double);
*/
}
}
/*
# string functions
POSIX offers some extra convenience functions to common string
operations which are not present in ANSI C.
*/
{
/*
# bzero
# bcopy
http://pubs.opengroup.org/onlinepubs/009695399/functions/bzero.html
http://stackoverflow.com/questions/17096990/why-use-bzero-over-memset
Same as memset, but deprecated and not ANSI.
Don't use it.
*/
/*
# strfmon
Monetary string formatting.
*/
{
const int size = 16;
char out[size];
strfmon(out, size, "%n", 1234.567);
printf("%s", out);
assert(strcmp(out, "1234.57") == 0);
}
}
/*
# strings.h
*/
{
/*
# ffs
Find First bit Set.
Has assembly support in many processors, e.g. `bsf` in x86.
https://en.wikipedia.org/wiki/Find_first_set
*/
{
assert(ffs(0) == 0);
assert(ffs(1) == 1);
assert(ffs(2) == 2);
assert(ffs(3) == 1);
}
}
/*
# times
Get real time, user time and system time.
*/
{
/* TODO0 example */
}
/*
#time.h
*/
{
/*
# strptime
Parse time string with given format and store it in a struct.
*/
{
}
}
/*
# File IO
# File descriptors
`int` identifier to a data stream.
Many file descriptors can point to a single file.
One very important property of file descriptors is the current position from which read and write shall operate.
Reads and writes move the current position forward.
# File descriptors vs ANSI C FILE objects
ANSI C supports only the concept of file pointers via the `FILE` macro.
POSIX extends ANSI C and contains both function that manipulate ANSI C `FILE` objects
and file descriptors, which are integers that identify a file descriptor for the OS.
Operations that use file desriptors:
- open, close, write
Operations that use FILE pointers:
- ANSI C `fopen`, `fclose`, `ftell`, etc. functions
Since they are specific to POSIX, functions that use file pointers often give more options
than ANSI C functions that use `FILE*` objects.
If you don't need that greater level of control,
just use the ANSI C functions for greater portability.
It is possible to convert freely to and from `FILE*` via fdopen and fileno.
# fdopen
Convert file descriptor to `FILE*`.
# fileno
Convert `FILE*` to file descriptor.
# open
man 2 open
Open file descriptors such as files
Returns an `int` (file descriptor number) instead of a file.
Interfaces:
int open(const char *pathname, int flags);
int open(const char *pathname, int flags, mode_t mode);
Flags. Must specify one and only of the following:
- `O_WRONLY`: write only
- `O_RDONLY`: read only.
Undefined behaviour with O_TRUNC.
TODO can be used with O_CREAT?
- `O_RDWR`: read and write
Other important flags.
- `O_APPEND`: If the file exists, open fd at the end of the file.
- `O_TRUNC`: If the file exists, open fd at the end of the file,
set its length to zero, discarding existing contents.
Undefined behaviour with O_RDONLY.
- `O_CREAT`: If the file does not exit, creat it, with permissions given in mode.
Mode must be specified when this flag is set, and is ignored if this is not set.
- `O_EXCL`: Used with O_CREAT, ensures that the caller creates the file.
The open is atomic; that is, no two opens can open at the same time.
If the file already exists, open will fail.
Useful if multiple programs may try to create the same file.
Mode can be specified via oring predefine permission values of type:
- S_IRWXU 00700 user (file owner) has read, write and execute permission
- S_IRUSR 00400 user has read permission
- S_IWUSR 00200 user has write permission
- S_IXUSR 00100 user has execute permission
For group and other: G and GRP, O and OTH.
Return value: `-1` on error and set perror.
# creat
Same as:
open(path, O_WRONLY|O_CREAT|O_TRUNC, mode);
# close
File descriptors occupy memory and are thus a finite resource.
When you are done with one, release it with a close call.
# write
Write to file descriptor, such as one representing a file gotten via `open`
or one representing a pipe obtained with `pipe`.
Increments the current position of the file descriptor.
For regular files, if this becomes greater than the current file size,
then the file size is increased as needed.
# return value
Returns number of bytes writen.
For regular files, POSIX does not say much when the number of bytes writen is less than the ammount required
and that usually only happens in bad cases:
- signal received in the middle of a write
If it receives a signal before writing anything, returns -1.
- no more space in the filesystem
- no permission to write such large files
For pipes, this may occur in less bad scenarios, for example if the pipe buffer is filled,
the write may either:
- be partial if `nbytes > PIPE_BUF`
- block until more space is available depending on the `O_NONBLOCK` state.
On error, return -1 and set errno.
It seems that POSIX does not say if zero return values on non-zero size write requests.
It is unlikely that an implementation will return 0, since that would make no progress,
and it should return `-1` and set errno instead to report error cases.
# atomicity of simultaneous writes
Writes of less than `PIPE_BUF` bytes cannot be interleaved with other writes.
Larger writes can.
TODO0 are writes to seekable files atomic? Seems not: <http://stackoverflow.com/questions/10650861/atomicity-of-write2-to-a-local-filesystem>
for pipes we know yes for writes smaller than PIPE_BUF.
# pwrite
Same as write, but writes to a given offset and does not update fd position.
It cannot be done on non seekable files.
# read
POSIX 7 docs: http://pubs.opengroup.org/onlinepubs/9699919799/functions/read.html
Read bytes from a file descriptor.
Interface:
ssize_t read(int fildes, void *buf, size_t nbyte);
The behavior of multiple concurrent reads on the same pipe, FIFO, or terminal device is unspecified.
If the starting position is at or after the end-of-file, 0 shall be returned.
If the value of `nbyte` is larger than {SSIZE_MAX}, the result is implementation-defined.
In practice this is rarely the case, because `SSIZE_MAX` is the size of a `size_t` type,
which is usually an integer, giving around 2Gb.
The exact behaviour of read depends on the fd type: pipes and regular files have slightly different rules.
# read pipe
When attempting to read from an empty pipe or FIFO:
- If no process has the pipe open for writing, read() shall return 0 to indicate end-of-file.
- If some process has the pipe open for writing and O_NONBLOCK is set,
read() shall return -1 and set errno to [EAGAIN].
- If some process has the pipe open for writing and O_NONBLOCK is clear,
read() shall block the calling thread until some data is written or
the pipe is closed by all processes that had the pipe open for writing.
# return value
Returns number of bytes read.
Quoting POSIX:
The value returned may be less than nbyte if:
- the number of bytes left in the file is less than nbyte
- the read() request was interrupted by a signal
- if the file is a pipe or FIFO or special file and has fewer
than nbyte bytes immediately available for reading.
Therefore on a regular file, this is how the end of file can be recognized.
On error return -1 and set errno.
# lseek
Like ANSI C fseek for file descriptors.
lseek cannot be done on certain file descriptor types which are not seekable,
for example on pipes.
# lseek after eof
If data is writen with offset after file size, file size is increased and data skipped reads `(int)0` (`'\0'`).
This contrasts with ANSI C fseek, in which this is undefined behaviour.
# fcntl
Manipulate a file descriptor.
Check if a fd is open: <http://stackoverflow.com/questions/12340695/how-to-check-if-a-given-file-descriptor-stored-in-a-variable-is-still-valid>
# dup
Duplicate file descriptor.
Same as:
fcntl(fildes, F_DUPFD, 0);
# send
Generalization of write:
<http://pubs.opengroup.org/onlinepubs/9699919799/functions/send.html>
TODO
# recv
Generalization of read:
<http://pubs.opengroup.org/onlinepubs/9699919799/functions/recv.html>
TODO
*/
{
int fd;
char in[] = "abcd";
int nbytes = strlen(in);
char *out = malloc (nbytes + 1);
char *fname = TMPFILE("open");
/* BAD forget O_CREAT on non-existent file gives ENOENT */
{
fd = open("/i/do/not/exist", O_RDONLY, S_IRWXU);
if (fd == -1) {
assert(errno == ENOENT);
} else {
assert(false);
}
}
/* BAD write on a O_RDONLY fd gives errno EBADF */
{
int fd;
char *fname = TMPFILE("write_rdonly");
fd = open(fname, O_WRONLY | O_CREAT | O_TRUNC, S_IRWXU);
if (fd == -1) {
perror("open");
exit(EXIT_FAILURE);
} else {
if (close(fd) == -1) {
perror("close");
exit(EXIT_FAILURE);
}
}
fd = open(fname, O_RDONLY);
if (fd == -1) {
perror("open");
exit(EXIT_FAILURE);
} else {
if (write(fd, "a", 1) == -1) {
assert(errno == EBADF);
} else {
assert(false);
}
if (close(fd) == -1) {
perror("close");
exit(EXIT_FAILURE);
}
}
}
/*
Open and write without truncate.
Output:
after write 1: abcd
after write 2: 01cd
*/
{
/* Set file to `abc`. */
{
fd = open(fname, O_WRONLY | O_CREAT | O_TRUNC, S_IRWXU);
if (fd == -1) {
perror("open");
exit(EXIT_FAILURE);
} else {
if (write(fd, in, nbytes) != nbytes) {
perror("write");
exit(EXIT_FAILURE);
}
if (close(fd) == -1) {
perror("close");
exit(EXIT_FAILURE);
}
}
}
/* Open and write to it without truncating. */
{
fd = open(fname, O_RDWR);
if (fd == -1) {
perror("open");
exit(EXIT_FAILURE);
} else {
if (write(fd, "01", 2) != 2) {
perror("write");
exit(EXIT_FAILURE);
}
}
}
/* Check the new result. */
{
if (lseek(fd, 0, SEEK_SET) != 0) {
perror("lseek");
exit(EXIT_FAILURE);
}
if (read(fd, out, nbytes) != nbytes) {
perror("read");
exit(EXIT_FAILURE);
} else {
/* The first two bytes were overwriten. */
assert(memcmp(out, "01cd", nbytes) == 0);
}
if (close(fd) == -1) {
perror("close");
exit(EXIT_FAILURE);
}
close(fd);
}
}
/*
`lseek` after EOF
*/
{
int fd;
char out[2];
fd = open(TMPFILE("lseek"), O_RDWR | O_CREAT | O_TRUNC, S_IRWXU);
if (fd == -1) {
perror("open");
exit(EXIT_FAILURE);
} else {
if (lseek(fd, 1, SEEK_SET) != 1) {
perror("lseek");
exit(EXIT_FAILURE);
}
if (write(fd, "a", 1) != 1) {
perror("write");
exit(EXIT_FAILURE);
}
/* Read after eof, return 0 and read nothing. */
if (read(fd, out, 1) != 0) {
assert(false);
}
if (lseek(fd, 0, SEEK_SET) != 0) {
perror("lseek");
exit(EXIT_FAILURE);
}
/* Byte 0 was never writen, so reading it returns `(int)0`. */
if (read(fd, out, 2) != 2) {
perror("read");
exit(EXIT_FAILURE);
} else {
assert(memcmp(out, "\0a", 2) == 0);
}
if (close(fd) == -1) {
perror("close");
exit(EXIT_FAILURE);
}
}
}
/*
File descriptors open by default to all processes.
Analogous to ANSI C `stdout`, `stdin` and `stderr`, except that the ANSI C `FILE*` objects.
# STDIN_FILENO
stdin
# STDOUT_FILENO
stdout
# STDERR_FILENO
stderr
*/
{
printf("STDIN_FILENO = %d\n", STDIN_FILENO);
printf("STDOUT_FILENO = %d\n", STDOUT_FILENO);
printf("STDERR_FILENO = %d\n", STDERR_FILENO);
}
/*
# aio family
# Asynchronous IO
# aio_cancel
# aio_error
# aio_fsync
# aio_read
# aio_return
# aio_suspend
# aio_write
Great soruce: <http://www.fsl.cs.sunysb.edu/~vass/linux-aio.txt>
In Linux, implemented with the system calls:
io_cancel(2) 2.6
io_destroy(2) 2.6
io_getevents(2) 2.6
io_setup(2) 2.6
io_submit(2) 2.6
*/
{
/* TODO */
}
}
/*
# link
Create hardlink to file.
If newfile exists, the link is not created, returns -1 and sets `errno = EEXIST`
# unlink
Delete file.
Is called unlink because what you are doing is not to directly remove a file from disk
but instead remove one hardlink for the data.
If the number of hardlinks to a data equals 0, it the data gets deleted.
If the given path does not exist `errno = ENOENT`.
*/
{
int fd;
char in[] = "a";
char in_new[] = "b";
int nbytes = strlen(in);
char *out = malloc(nbytes);
char *oldpath = TMPFILE("link_old");
char *newpath = TMPFILE("link_new");
/* Create old. */
fd = open(oldpath, O_WRONLY | O_CREAT | O_TRUNC, S_IRWXU);
if (fd == -1) {
perror("open");
exit(EXIT_FAILURE);
} else {
if (write(fd, in, nbytes) != nbytes) {
perror("write");
exit(EXIT_FAILURE);
}
if (close(fd) == -1) {
perror("close");
exit(EXIT_FAILURE);
}
}
// Ensure that the new path does not exist
// `ENOENT` is ok since the path may not exist
if (unlink(newpath) == -1 && errno != ENOENT) {
perror("link");
exit(EXIT_FAILURE);
}
// Make the hardlink.
if (link(oldpath, newpath) == -1) {
perror("link");
exit(EXIT_FAILURE);
}
// Write to new.
fd = open(newpath, O_WRONLY);
if (fd == -1) {
perror("open");
exit(EXIT_FAILURE);
} else {
if (write(fd, in_new, nbytes) != nbytes) {
perror("write");
exit(EXIT_FAILURE);
}
if (close(fd) == -1) {
perror("close");
exit(EXIT_FAILURE);
}
}
//assert that it reflected on old
fd = open(oldpath, O_RDONLY);
if (fd == -1) {
perror("open");
exit(EXIT_FAILURE);
} else {
if (read(fd, out, nbytes) != nbytes) {
perror("read");
exit(EXIT_FAILURE);
}
assert(memcmp(out, in_new, nbytes) == 0);
if (close(fd) == -1) {
perror("close");
exit(EXIT_FAILURE);
}
}
free(out);
}
/*
# symlink
Create symbolic link.
TODO0 example
*/
{
}
/*
# select
Wait for one of multiple file descriptors to become available for some operation.
Sounds like server implementation!
*/
/* # Pathname operations */
{
/*
# realpath
Return:
- absolute path
- cannonical: does not contain `.` nor `..`.
Interface:
char *realpath(const char *restrict file_name,
char *restrict resolved_name);
The function does completelly different things depending if resolved_name is NULL or not:
- `resolved_name == NULL`: realpath mallocs the path for you and returns it.
You have to free it in the future.
This is a good options if you don't already have a buffer of the right size, since calculating the required buffer size
would be annoying (would require calling `pathconf`).
- `resolved_name == NULL`: the pathname is copied to `resolved_name`.
You must make sure that you have enough space there.
This is a good option if you already have a large enough buffer laying around,
since it does not require dynamic allocation.
Of course, ensuring that your buffer is large enough means doing messy `pathconfs` at some point.
*/
{
char *rp = realpath(".", NULL);
if (rp) {
printf("realpath(\".\") = %s\n", rp);
} else {
perror("realpath");
exit(EXIT_FAILURE);
}
free(rp);
}
/*
# dirname # basename
p may be modified memory is statically allocated
and may change on next dirname/basename call.
TODO what is p
behaviour:
path dirname basename
---------- -------- ---------
"/usr/lib" "/usr" "lib"
"/usr/" "/" "usr"
"usr" "." "usr"
"/" "/" "/"
"." "." "."
".." "." ".."
*/
{
char p[1024];
char* res;
strcpy(p, "a/b");
res = dirname(p);
assert(strcmp(res, "a") == 0);
strcpy(p, "a/b");
res = basename(p);
assert(strcmp(res, "b") == 0);
}
}
/*
# File and directory operations
there is no standard portable way of doing most them:
<http://www.devarticles.com/c/a/Cplusplus/Directories-in-Cplusplus/>
posix alternatives:
- portable semi heavyweight: booost: # include <boost/filesystem/operations.hpp>
- portable lightweight: dirent.h
*/
{
/*
# stat family
Get info on paths.
Return value: `0` on success, other constants on errors.
If you get a `0`, you know the file exists!
This is not however the best way to check if a file exists since it incurs the large overhead
of getting the parameters. Use `access` for that instead.
This fills in the `struct stat` given by pointer.
The family contains the following variants:
- stat: takes string path. Grouped under fstatat.
- lstat: does not dereference symbolic links. Grouped under fstatat.
- fstat: takes fd.
- fstatat: can't understand, does not seem important.
# struct stat
fields:
- dev_t st_dev Device ID of device containing file.
- ino_t st_ino File serial number.
- mode_t st_mode Mode of file
- nlink_t st_nlink Number of hard links to the file.
- uid_t st_uid User ID of file.
- gid_t st_gid Group ID of file.
- dev_t st_rdev Device ID (if file is character or block special).
- off_t st_size For regular files, the file size in bytes.
For symbolic links, the length in bytes of the
pathname contained in the symbolic link.
For a typed memory object, the length in bytes.
For other file types, the use of this field is
unspecified.
- struct timespec st_atim Last data access timestamp.
- struct timespec st_mtim Last data modification timestamp.
- struct timespec st_ctim Last file status change timestamp.
- blksize_t st_blksize A file system-specific preferred I/O block size
for this object. In some file system types, this
may vary from file to file.
- blkcnt_t st_blocks Number of blocks allocated for this object.
# Find if a file exists.
In *nix, you often cannot be sure if a file or directory exists,
because to do that you must have permission to list all of its parent dirs.
The only thing you can say is that a path is accessible or not.
Using stat is a good way to do that.
*/
{
char in[] = "123\n";
char fname[] = TMPFILE("stat");
struct stat s;
// Create the file.
int fd = open(fname, O_WRONLY | O_CREAT, S_IRWXU);
int nbytes = strlen(in);
if (fd != -1) {
if (write(fd, in, nbytes) != nbytes) {
assert(false);
} else {
// Assert that file exists.
assert(stat(fname, &s) == 0);
// View/assert the fields of the stat struct.
assert(s.st_size == nbytes);
}
close(fd);
} else {
assert(false);
}
}
/*
# access
http://pubs.opengroup.org/onlinepubs/9699919799/functions/access.html
Check if file or directory exists and or has a given permission (rwx):
- `R_OK`
- `W_OK`
- `X_OK`
- `F_OK`: file exists
If the access is not permitted, errno is still set even if this call did not give an error.
TODO vs stat?
*/
{
char *exist = realpath(".", NULL);
if(access(exist, F_OK) == -1) {
perror("access");
assert(false);
}
free(exist);
char *dont_exist = "/i/dont/canot/must/not/exist.asdf";
if(access(dont_exist, F_OK) == -1) {
perror("access(dont_exist, F_OK)");
} else {
assert(false);
}
}
/* # mkdir */
{
struct stat s;
char fname[] = TMPFILE("mkdir");
// Remove the file if it exists.
if(stat(fname, &s) == 0)
rmdir(fname);
// Make the dir and check for error.
if(mkdir(fname, 0777) == -1)
assert(false);
}
/* # rmdir */
{
mkdir("rmdir", 0777);
if(rmdir("rmdir") == -1)
assert(false);
}
/*
# ls
Opendir is the basis for `ls`.
# opendir
Open a directory for reading.
# readdir
Get next directory entry, or NULL if over.
# dirent
<http://pubs.opengroup.org/onlinepubs/009604599/basedefs/dirent.h.html>
*/
{
DIR* dp;
struct dirent* entry;
dp = opendir(".");
if (dp == NULL) {
perror("opendir");
} else {
printf("opendir:\n");
while ((entry = readdir(dp)) != NULL) {
printf(" %s\n", entry->d_name);
}
}
}
}
/*
# getrlimit
Returns the maximum value for a given resource.
Linux has a system call with that name.
There are two types of limits:
- soft: can be increased by any process to any value lower than the hard maximum
- hard: only processes with special privileges may increase the hard limit
If a resource goes over the soft limit, the kernel may choose to kill the process
Interfaces:
int getrlimit(int resource, struct rlimit *rlp);
int setrlimit(int resource, const struct rlimit *rlp);
- resource: see the docs for a description of each possible value
- rlp: struct of type:
struct rlimit {
rlim_t rlim_cur // The current (soft) limit.
rlim_t rlim_max // The hard limit.
}
where `rlim_t` is an unsigned integer
*/
{
struct rlimit limit;
if (getrlimit(RLIMIT_DATA, &limit) == -1) {
perror("getrlimit(RLIMIT_DATA, ...) failed");
exit(EXIT_FAILURE);
} else {
//maximum process memory in bytes
if (limit.rlim_max == RLIM_INFINITY) {
//RLIM_INFINITY means that no limit is imposed on the resource
puts("RLIMIT_DATA: no limit imposed");
} else {
printf(
"RLIMIT_DATA\n soft = %ju\n hard = %ju\n",
(uintmax_t)limit.rlim_cur,
(uintmax_t)limit.rlim_max
);
}
}
/* OK, enough of error checking from now on. */
printf("RLIM_INFINITY = %ju\n", (uintmax_t)RLIM_INFINITY);
/* maximum total CPU usage in seconds. */
getrlimit(RLIMIT_CPU, &limit);
printf(
"RLIMIT_CPU\n soft = %ju\n hard = %ju\n",
(uintmax_t)limit.rlim_cur,
(uintmax_t)limit.rlim_max
);
/* Maximum file size in bytes. */
getrlimit(RLIMIT_FSIZE, &limit);
printf(
"RLIMIT_FSIZE\n soft = %ju\n hard = %ju\n",
(uintmax_t)limit.rlim_cur,
(uintmax_t)limit.rlim_max
);
/* Number of file descriptors: */
getrlimit(RLIMIT_NOFILE, &limit);
printf(
"RLIMIT_NOFILE\n soft = %ju\n hard = %ju\n",
(uintmax_t)limit.rlim_cur,
(uintmax_t)limit.rlim_max
);
}
/*
# limits.h
This header exists in ANSI C, and POSIX extends it with several values.
Defines current and possible maximuns, minimums for several resources.
Some resources cannot cannot be evaluated statically.
For example the maximum path length depends on which directory we are talking about,
since diferent directories can be on differnet mount points.
Also some resources can change maximum values at anytime while the program is executing.
In those cases, limits defines a KEY value which can be passed to a function that gets
the actual values for a given key, for example pathconf or sysconf.
For resources that have fixed values, this header furnishes them directly.
*/
{
// Static macros.
{
// TODO what is it
printf("NL_ARGMAX = %d\n", NL_ARGMAX);
// Maximum value that fits into a `size_t`.
printf("SSIZE_MAX (Mib) = %ju\n", (uintmax_t)SSIZE_MAX / (1 << 20));
}
/*
# sysconf
Get lots of info on the system configuration
Meanings for the constants can be found under
the `limits.h` and `unistd.h` corresponding variables
If the value can be negative, it is necessary to check errno changes for error.
It seems
# maximum path length
This is needed often when you need to deal with paths names.
Sources:
- <http://stackoverflow.com/questions/2285750/size-of-posix-path-max>
Keep in mind that this value can vary even while a program is running,
and depends of the underlying filesystem, and therefore of the direcotory you are in.
As a consequence of this, it does not make sense to have a macro constant and use it to create
fixed variable arrays: a function is needed, and memory must be allocated with malloc.
*/
{
// Number of processors:
printf("_SC_NPROCESSORS_ONLN = %ld\n", sysconf(_SC_NPROCESSORS_ONLN));
// Maximum lengh of command line arguments + environment variables:
printf("_SC_ARG_MAX (MiB) = %ld\n", sysconf(_SC_ARG_MAX) / (1 << 20));
// TODO find the number of processors / cpus / cores: not possible without glibc extension?
// <http://stackoverflow.com/questions/2693948/how-do-i-retrieve-the-number-of-processors-on-c-linux>
}
/*
# pathconf
Similar to sysconf, but for parameters that depend on a path, such as maxium filename lengths.
*/
{
/* Max basename in given dir including trailling null: */
printf("pathconf(\".\", _PC_NAME_MAX) = %ld\n", pathconf(".", _PC_NAME_MAX));
/* Max pathname in (TODO this is per device?) */
printf("pathconf(\".\", _PC_PATH_MAX) = %ld\n", pathconf(".", _PC_PATH_MAX));
}
}
/*
# user information
Once use have uids for processes, you can querry standard user information
which was traditionally stored in the `/etc/passwd` file.
*/
{
/*
# getpwuid
You can get those information either by username or by uid:
# include <pwd.h>
struct passwd *getpwuid(uid_t uid);
struct passwd *getpwnam(const char *name);
The struct returned is:
struct passwd {
passwd Member // Description
char *pw_name // The users login name
uid_t pw_uid // The UID number
gid_t pw_gid // The GID number
char *pw_dir // The users home directory
char *pw_gecos // The users full name
char *pw_shell // The users default shell
}
Which contains all the required user metadata specified by POSIX.
*/
{
uid_t uid = getuid();
struct passwd* info = getpwuid(uid);
if (info == NULL) {
perror("getpwuid failed");
exit(EXIT_FAILURE);
} else {
puts("getpwuid");
printf(" pw_name = %s\n", info->pw_name );
printf(" pw_uid = %d\n", info->pw_uid );
printf(" pw_gid = %d\n", info->pw_gid );
printf(" pw_dir = %s\n", info->pw_dir );
printf(" pw_gecos = %s\n", info->pw_gecos);
printf(" pw_shell = %s\n", info->pw_shell);
}
}
/*
# getpwent
Iterate a list of all passwd structures.
First call gets the first, every call gets the next.
To start from beginning again do:
void setpwent(void);
When you are done, free any associated resources with:
endpwent()
*/
{
puts("all users:");
struct passwd* info;
info = getpwent();
while (info != NULL) {
printf(" %s\n", info->pw_name);
info = getpwent();
}
endpwent();
}
}
/*
# uname
Get information about the current computer using `uname`.
Unsurprisingly, it is the same information given by the POSIX utility `uname`.
*/
{
struct utsname info;
if (uname(&info) == -1) {
perror("uname failed");
exit(EXIT_FAILURE);
} else {
puts("uname");
printf(" sysname = %s\n", info.sysname );
printf(" nodename = %s\n", info.nodename);
printf(" release = %s\n", info.release );
printf(" version = %s\n", info.version );
printf(" machine = %s\n", info.machine );
}
}
/*
# process info
# getpid
Each process has an unique identifying integer called PID.
# getuid
Each process has user information associated to it
which determine what the process can or not.
There are two types of uid and gid: real and effective:
- real is always of who executes the program
- effective may be different depending on the suid and sgid bits
# setuid
# getguid
Like `uid` versions but for group.
# getppid
Get parent's pid.
It seems that it is not possible to list all children of a process in POSIX:
<http://stackoverflow.com/questions/1009552/how-to-find-all-child-processes>
# getsid
# setsid
# setpgid
# getpgid
# setpgrp
# getpgrp
# Session
# Process group
TODO http://en.wikipedia.org/wiki/Process_group
*/
{
uid_t uid = getuid();
uid_t euid = geteuid();
gid_t gid = getgid();
gid_t egid = getegid();
pid_t pid = getpid();
printf("getpid() = %ju\n", (uintmax_t)pid );
printf("getuid() = %ju\n", (uintmax_t)uid );
printf("geteuid() = %ju\n", (uintmax_t)euid );
printf("getgid() = %ju\n", (uintmax_t)gid );
printf("getegid() = %ju\n", (uintmax_t)egid );
printf("getppid() = %ju\n", (uintmax_t)getppid());
/*
# getcwd
pwd
# root directory
As of POSIX 7, this concept is not available.
It is implemented as a Glibc extension under `_BSD_SOURCE`.
*/
{
const int n = 1 << 10;
char buf[n];
if (getcwd(buf, n) == NULL) {
perror("getcwd");
} else {
printf("getcwd() = %s\n", buf);
}
}
}
/*
# getpriority
Each process, user and group has a priority associated to it.
Those priorities are commonly called *nice* values on UNIX, since
the higher the nice, the less time it takes (it is nicer to other processes)
Any process can reduce its priority.
Only a priviledged process can increaes its priority,
even if the process just decreased it himself.
POSIX does not fix the nice range, but it does specify that:
- lower values are more favorable
- the values must be between `-{NZERO}` and x{NZERO}-1`.
where `NZERO` is implementation defined parameter.
The minimum value for NZERO if 20, it is also the most common.
The actual effect of priority is undefined.
`nice`:
int nice(int incr)
- incr: how much to increase the nice value
- return: the new nice value after the increase
`getpriority`:
int getpriority(int which, id_t who);
- `which`:
- PRIO_PROCESS:
- PRIO_PGRP: TODO
- PRIO_USER: TODO
- `who`: pid, uid or gid depending on which. `0` means current.
# error checking
On error, returns `-1` and errno set to indicate the error.
Therefore simply checking the return value is not enough to detect an error
since `-1` is a valid return value.
Therefore, to do error checking one *must* check `errno != 0`:
- set `errno = 0`
- make the call
- there is an error iff ret = -1 errno != 0.
# setpriority
Return value is the same as getpriority after the modification.
# nice
Same as setpriority, but only for `PRIO_PROCESS` but increments
(or decrements) the value instead of setting it to an absolute value.
Return value is the same as getpriority after the modification.
Can only be negative if we have `sudo`, since a negative value means to increase the priority.
*/
{
int prio;
printf("NZERO = %d\n", NZERO);
errno = 0;
prio = getpriority(PRIO_PROCESS, 0);
if (prio == -1 && errno != 0) {
perror("getpriority(PRIO_PROCESS, 0)");
} else {
printf("getpriority(PRIO_PROCESS, 0) = %d\n", prio);
}
errno = 0;
prio = getpriority(PRIO_PGRP, 0);
if (prio == -1 && errno != 0) {
perror("getpriority(PRIO_PGRP, 0)");
} else {
printf("getpriority(PRIO_PGRP, 0) = %d\n", prio);
}
errno = 0;
prio = getpriority(PRIO_USER, 0);
if (prio == -1 && errno != 0) {
perror("getpriority(PRIO_USER, 0)");
} else {
printf("getpriority(PRIO_USER, 0) = %d\n", prio);
}
errno = 0;
prio = nice(0);
if (prio == -1 && errno != 0) {
perror("nice(0)");
} else {
printf("nice(0) = %d\n", nice(0));
}
//ok, tired of errno checking:
printf("nice(0) = %d\n", nice(0));
printf("nice(1) = %d\n", nice(1));
printf("nice(0) = %d\n", nice(0));
errno = 0;
prio = nice(-1);
if (prio == -1 && errno != 0) {
//if not root we end up here
perror("nice(-1)");
} else {
printf("nice(-1) = %d\n", prio);
}
}
/*
# sched.h
Get or set scheduler information
POSIX 7 specifies four scheduling policies, more can be defined by the implementation
The precise decription of those policies can be found under System interfaces > Scheduling policies.
TODO what happens if there is both a FIFO and a RR process running?
- `SCHED_FIFO`:
First in first out. Process runs untill it finishes, blocking the CPU while it runs.
An infinite loop could be catastrophic.
In linux, applied to realtime process.
- `SCHED_RR`:
Round robin.
Assign time slices proportional to prio and turn around the pie.
In linux, applied to realtime process.
- `SCHED_SPORADIC`:
TODO0
Optional, so don't rely on it.
- `SCHED_OTHER`:
Any other type of scheduling.
In linux, includes SCHED_NORMAL, the normal scheduling policy.
POSIX explicitly says that it is implementation defined what happens
when there are both process of `SCHED_OTHER` and another type at the same time.
# sched_getscheduler
`pid_t` for given pid, `0` for current process
# sched_get_priority_max
Get maximum possible priority for a given policy.
# sched_get_priority_min
*/
{
printf("SCHED_FIFO = %d\n", SCHED_FIFO);
printf("sched_get_priority_max(SCHED_FIFO) = %d\n", sched_get_priority_max(SCHED_FIFO));
printf("sched_get_priority_min(SCHED_FIFO) = %d\n", sched_get_priority_min(SCHED_FIFO));
printf("SCHED_RR = %d\n", SCHED_RR);
printf("sched_get_priority_max(SCHED_RR) = %d\n", sched_get_priority_max(SCHED_RR));
printf("sched_get_priority_min(SCHED_RR) = %d\n", sched_get_priority_min(SCHED_RR));
//printf("SCHED_SPORADIC = %d\n", SCHED_SPORADIC);
printf("SCHED_OTHER = %d\n", SCHED_OTHER);
printf("sched_get_priority_max(SCHED_OTHER) = %d\n", sched_get_priority_max(SCHED_OTHER));
printf("sched_get_priority_min(SCHED_OTHER) = %d\n", sched_get_priority_min(SCHED_OTHER));
printf("sched_getscheduler(0) = %d\n", sched_getscheduler(0));
/*
# sched_setscheduler()
You need root permissions to change to higher priority modes such as from `SCHED_NORMAL` to `SCHED_FIFO` or `SCHED_RR`.
*/
{
int policy = SCHED_FIFO;
struct sched_param sched_param = {
.sched_priority = 99
};
if (sched_setscheduler(0, policy, &sched_param) == -1) {
perror("sched_setscheduler");
//exit(EXIT_FAILURE);
} else {
assert(sched_getscheduler(0) == policy);
}
}
/*
Bogging the computer down.
Time to have some destructive fun by making your computer bog down because of infinite loops on real time processes.
This will generate an infinite number of `SCHED_FIFO` classes with maximum priority.
*SAVE ALL DATA BEFORE DOING THIS!!!*
Needs root to work.
I recommend running this with music on the background to see the music break up.
The mouse also becomes very irresponsive.
However userspace system does not halt. It seems that POSIX does not specify how operating systems
should deal with different simultaneous scheduling policies, and my Linux does not let SCHED_FIFO run 100% of the time.
maybe as explained here: <http://stackoverflow.com/a/10290305/895245>.
I could therefore kill the parent and all children with a C-C on the starting terminal.
*/
if (0) {
int policy = SCHED_FIFO;
struct sched_param sched_param = {
.sched_priority = sched_get_priority_max(policy)
};
if (sched_setscheduler(0, policy, &sched_param) == -1) {
perror("sched_setscheduler");
} else {
while (1) {
pid_t pid = fork();
if (pid == -1) {
perror("fork");
assert(false);
} else {
if (pid == 0)
break;
}
}
while (1);
}
}
}
/*
# IPC
inter process communication
the basic ways are:
at startup:
- command line arguments
- environment variables
during execution:
- pipes
- regular files
- signals
- shared memory
- sockets
*/
/*
# pipe
Must read man page:
man 7 pipe
Unidirectional data transfer from the write side to the read side.
The write side in on one process (producer)
and the read side on another one (consumer).
The consumer and producer can be the same process, but in that case pipes are useless.
There are two types of pipes:
- unnamed pipes
- FIFOs
Both pipe sides are represented by either a file descriptor int or by a `FILE*`,
and many operations on them are the same as operation on disk files using
functions such as `write` and `read`.
# Pipes are RAM only
Pipes are meant to be implementable on RAM only without using the filesystem.
This means that reading and writing to pipes is potentially much faster
than reading and writing to files.
The price to pay is that some operations
such as `ftell` and `fseek` are not possible.
There are also fcntl flags which cannot be applied to pipes.
# Read and write operations on pipes
Read and write operations may be slightly differnt depending on the file descriptor type.
This is specially the case for pipes vs regular files with respect to blocking.
For example, pipes usually block if there is no more data available, and wait for data to become available.
See the documentation of read and write for the details.
# Forbidden pipe operations
Since pipes are meant to be implemented on RAM only, pipes cannot be rewinded.
For example, using `ftell` on a pipe generate `errno=ESPIPE`.
Therefore, once the data has been read from the read side it can be discareded by the OS,
preventing all its RAM from being used up easily.
It seems that the only way to ask for forgiveness rather than permission and just do a `ftell`.
<http://stackoverflow.com/questions/3238788/how-to-determine-if-a-file-descriptor-is-seekable>
# Advantages over using files
Potentially faster because possible to be RAM only.
If the read happens before the write it blocks untill the write is done.
This lets the OS manage all the synchronization.
# Pipe capacity
There is a maximum data ammount that can be writen to a pipe.
If a process tries to write more data than the maximum to the pipe, it blocks until
part of the data is read.
This is a good source of deadlocks!
It seems that it is not possible to get the maximum pipe capacity:
<http://pubs.opengroup.org/onlinepubs/009695399/functions/write.html#tag_03_866>.
*/
{
/*
# Unnamed pipe
Can be created either via `popen` or `pipe`
A single process must start two children process:
the data source and the data consumer and connect them
Advantages over named pipes:
- simple: no need to agree on a filename to communicate over
- secure: other process cannot se the data as they could in a file
i think it is not possible to know if a file pointer
is open for reading or writtin besides looking at how
it was created
workflow:
- child fills the buffer, then parent takes control
- child fills ...
*/
/*
# popen
<http://pubs.opengroup.org/onlinepubs/9699919799/functions/popen.html>
man popen
Conveninence method that does:
- `fork`
- `exec`
- `pipe`
to / from the current process and a child.
It is:
- more general than `system`, as it allows to either set the stdin of the process,
or get it's stdout (but not both since pipes are unidirectional)
As a consequence, unlike `system` it does not automatically wait
for the command to return on the parent: you must wait for the process to end,
the most convenient way in thise case being with `pclose`, which takes the
`FILE*` returned by this function. But the `wait` family would also work
if you get the child PID.
- less general than `pipe`
The output of popen is put on an unnamed pipe, which is accessible via
ANSI C FILE type returned by the function, instead of POSIX file descriptor (integers)
Therefore you must use ANSI C file io functions like `fopen` or `fclose` with it,
instead of the more low level POSIX `open` or `write` family.
errno may or not be set depending on what caused the error.
# pclose
Wait for process created by `popen` to terminate.
Returns child exit status if the process was not waited for before,
`-1` otherwise.
*/
{
/* Read from stdout of command and get its exit status. */
{
/* popen uses ANSI C fread which uses ANSI C FILE type: */
FILE *read_fp;
char buffer[BUFSIZ + 1];
char cmd[1024];
int chars_read;
int exit_status;
int read_cycles = 0;
int desired_read_cycles = 3;
int desired_last_char_read = 1;
assert(desired_last_char_read < BUFSIZ);
sprintf(
cmd,
"for i in `seq %ju`; do echo -n a; done",
(uintmax_t)(desired_read_cycles - 1) * BUFSIZ + desired_last_char_read
);
read_fp = popen(cmd, "r");
if (read_fp == NULL) {
printf("popen failed\n");
exit(EXIT_FAILURE);
} else {
do {
chars_read = fread(buffer, sizeof(char), BUFSIZ, read_fp);
buffer[chars_read] = '\0';
printf("======== n bytes read: %d\n", chars_read);
/* If you want to see a bunch of 'a's... */
/*printf("%s\n", buffer); */
read_cycles++;
} while (chars_read == BUFSIZ);
exit_status = pclose(read_fp);
if (exit_status == -1) {
printf("pclose failed\n");
exit(EXIT_FAILURE);
}
assert(read_cycles == desired_read_cycles);
assert(chars_read == desired_last_char_read);
assert(exit_status == 0);
}
}
/* Write to stdin of command, its stdout goes to current stdout. */
{
FILE *write_fp;
char buf[BUFSIZ];
int exit_status;
memset(buf, 'b', BUFSIZ);
/*
Counts how many charaters were given to stdin
using many sh features to make it clear that a sh interpreter is called.
*/
write_fp = popen("read A; printf 'popen write = '; printf $A | wc -c", "w");
if (write_fp == NULL) {
assert(false);
} else {
/* TODO is safe to write twice BUFSIZ without a newline in the middle? */
fwrite(buf, sizeof(char), BUFSIZ, write_fp);
fwrite(buf, sizeof(char), BUFSIZ, write_fp);
/* Print `2 * BUFSIZ` command waits until pipe close. */
exit_status = pclose(write_fp);
assert(exit_status == 0);
}
}
}
/*
# pipe
Create unnamed pipes.
Very close to the Linux pipe system call.
Differences from popen:
- does not use a shell, avoiding many of its problems
- uses two integer file descriptors, one for each end of the pipe,
instead of ANSI C `FILE` typedef.
Therefore you manipulate pipes with file descriptor functions
like `open` and `write` instead of ANSI C `fopen` family.
This gives you more control over the operations, but is harder to setup.
Typically used with fork + exec.
*/
{
/*
Producer and consumer are the same process.
Useless but a good simple example.
*/
{
int nbytes;
int pipes[2];
char data[] = "123";
char buf[BUFSIZ + 1];
if (pipe(pipes) == -1) {
perror("pipe");
exit(EXIT_FAILURE);
} else {
nbytes = write(pipes[1], data, strlen(data));
assert(nbytes != -1);
assert((unsigned int)nbytes == strlen(data));
nbytes = read(pipes[0], buf, BUFSIZ);
assert(nbytes != -1);
assert((unsigned int)nbytes == strlen(data));
buf[nbytes] = '\0';
assert(strcmp(buf, data) == 0);
}
}
/*
Parent writes to child.
This works because if ever read happens before, it blocks.
*/
{
int nbytes;
int file_pipes[2];
const char data[] = "123";
char buf[BUFSIZ + 1];
pid_t pid;
if (pipe(file_pipes) == -1) {
perror("pipe");
exit(EXIT_FAILURE);
} else {
fflush(stdout);
pid = fork();
if (pid == -1) {
perror("fork");
exit(EXIT_FAILURE);
} else {
if (pid == 0) {
/* Child only. */
/* If read happens before write, it blocks because there is no data. */
nbytes = read(file_pipes[0], buf, BUFSIZ);
printf("pipe child. data: %s\n", buf);
exit(EXIT_SUCCESS);
}
/* parent only */
nbytes = write(file_pipes[1], data, strlen(data));
assert((size_t)nbytes == strlen(data));
int status;
wait(&status);
/* parent only after child */
assert(status == EXIT_SUCCESS);
}
}
}
}
/*
# FIFO
aka named pipes
appear on the filesystem
therefore can be accessed as by any process who sees it
and has enough priviledge level
are however faster than writting to files,
since everything happens on RAM
cannot open for rw
application: simple client/servers!
created with mkfifo
# mkfifo
Create a FIFO.
# mknod
Create a FIFO, character device, block device, directory or file.
int mknod(const char *path, mode_t mode, dev_t dev);
POSIX only demands that this work for FIFOs, all other are optional suggested interfaces.
The type is ored into mode and can be:
S_IFIFO
S_IFCHR
S_IFDIR
S_IFBLK
S_IFREG //regular file
however implementation need only support `S_IFIFO`.
`dev` represents the device corresponding to the file, and must be `0` in the case of `S_IFIFO`,
since FIFOs have no associated devices.
Linux implements all the options except `S_IFDIR` via the `sys_mknod` syscall.
It also adds `S_IFSOCK` for UNIX domain sockets. Glibc provides a wrapper for all the extended linux
functionality.
*/
{
}
/*
# PIPE_BUF
Maximum size that guarantees that a pipe write operation will be atomic.
PIPE_BUF is guaranteed to be at least 512.
See `man 7 pipe` for an explanation.
Quoting POSIX 7:
If path refers to a FIFO, or fildes refers to a pipe or FIFO,
the value returned shall apply to the referenced object.
If path or fildes refers to a directory, the value returned shall apply to any FIFO that exists
or can be created within the directory.
If path or fildes refers to any other type of file,
it is unspecified whether an implementation supports an association of the variable name with the specified file.
Conclusions:
- the PIPE_BUF of an unnamed pipe can only be determined after it is created via `fpathconf`.
- the PIPE_BUF of FIFOs is the same for all fifos on given directory.
It can be retreived with `pathconf`.
*/
{
long pipe_buf;
int pipes[2];
//unnamed pipe
if (pipe(pipes) == -1) {
perror("pipe");
exit(EXIT_FAILURE);
} else {
pipe_buf = fpathconf(pipes[0], _PC_PIPE_BUF);
printf("PIPE_BUF pipes[0] = %ld\n", pipe_buf);
assert(pipe_buf >= 512);
pipe_buf = fpathconf(pipes[1], _PC_PIPE_BUF);
printf("PIPE_BUF pipes[1] = %ld\n", pipe_buf);
assert(pipe_buf >= 512);
}
//directory
pipe_buf = pathconf(".", _PC_PIPE_BUF);
printf("PIPE_BUF \".\" = %ld\n", pipe_buf);
assert(pipe_buf >= 512);
}
}
/*
# shared memory
Memory that can be accessed by multiple separate processes.
In this example, both parent and child processes access the same shared memory.
*/
{
int shmid;
int *shmem;
/*
# shmget
Allocate shared memory
int shmget(key_t key, size_t size, int shmflg);
- key: TODO0 what is this
- size: num of bytes
- shmflg: permission flags like for files + other specific flags
IPC_CREAT is required to allocate the memory
Return:
- negative if error
- unique identifier of memory if positive
*/
{
shmid = shmget((key_t)1234, sizeof(int) * 2, IPC_CREAT | S_IRWXU | S_IRWXO);
assert(shmid >= 0);
}
/*
# shmat
Attach shared memory to current process so it can be used afterwards
void *shmat(int shm_id, const void *shm_addr, int shmflg);
- shm_id: memory id given to shmget
- shm_id: where to map to. Usual choice: `NULL` so the system choses on its own.
- shm_flg:
*/
{
shmem = shmat(shmid, NULL, 0);
if (shmem == (void*)-1) {
assert(false);
} else {
shmem[0] = 1;
fflush(stdout);
pid_t pid = fork();
if (pid < 0) {
assert(false);
} else {
// Child only.
if (pid == 0) {
// Child inherits attached memory.
shmem[0]++;
// Detach from child.
assert(shmdt(shmem) == 0);
exit(EXIT_SUCCESS);
}
int status;
wait(&status);
// Parent only after child.
assert(status == EXIT_SUCCESS);
assert(shmem[0] == 2);
/*
# shmdt
Detach shared memory from current process:
int shmdt(void* shmem);
Each process should detach it separatelly before deleting the memory.
*/
{
// Detach from parent.
assert(shmdt(shmem) == 0);
}
/*
# shmctl
Controls the shared memory, doing amongst other things its deletion
int shmctl(int shm_id, int command, struct shmid_ds *buf);
- shm_id returned by shmget
- command is one of the following:
- `IPC_STAT`: get parameters of memory into buf
- `IPC_SET`: set parameters of memory to match buf
- `IPC_RMID`: delete memory.
It must be detached from all processes, or you get unspecified behaviour.
- buf the following struct:
struct shmid_ds {
uid_t shm_perm.uid;
uid_t shm_perm.gid;
mode_t shm_perm.mode;
}
it can be `NULL` for `PIC_RMID`
- Return: 0 on success, -1 on failure.
*/
{
assert(shmctl(shmid, IPC_RMID, NULL) == 0);
}
}
}
}
}
/*
# thread synchronization
Threads can be synchronized via:
- semaphores
- mutexes
Threads have the specific synchronization mechanisms:
# mutex
*/
{
/*
# sched_yield
excplicitly tell scheduler to schedule another process
*/
{
sched_yield();
}
}
/*
# sync
Makes all cached writes to all filesystems.
OSes may keep disk writes for later for efficienty, grouping several writes into one.
This explicitly tells the OS to write everything down.
`fclose` only flushes data from internal C library bufferes to the OS,
but does not guarantee that the OS has written the data to disk.
TODO what is an application for this, except before shutting down the system?
# fsync
Same as sync, but only for filesystem containing given fd.
*/
{
sync();
int fd = open(__FILE__, O_RDONLY);
fsync(fd);
close(fd);
}
/*
# termios.h
Terminal management
*/
{
//TODO0
}
/*
# terminal
Some POSIX functions deal with the controlling terminal which called the program if any.
# getlogin
Get login name of controlling terminal
This is different from `getuid` since it looks at the controlling terminal,
and not at processes specific information.
*/
{
char* login = getlogin();
if (login == NULL) {
perror("getlogin failed");
} else {
printf("getlogin() = %s\n", getlogin());
}
}
return EXIT_SUCCESS;
}