User Tools

Site Tools


Sidebar

os_cp:open_fork

Creating, opening and closing files

To create or open a file, POSIX defines:
Needs header:
fcntl.h
int open(const char *pathname, int flags)
int open(const char *pathname, int flags, mode_t mode)

and a creat function that is a shorthand to open(pathname, O_WRONLY|O_CREAT|O_TRUNC, mode).

pathname is a path.

mode is used only if open creates a new file, and it defines its permissions. Either use octal number, or use symbolic constants described in the manual.

flags is a rat's nest.
flags must contain exactly one of the following flags: O_RDONLY, O_WRONLY, or O_RDWR that choose whether file is opened for reading, writing or both.
flags may additionally contain other flags, including the following:

  • O_APPEND sets file position to the end of a file before every write
  • O_TRUNC (shall be used only in conjunction with O_WRONLY or O_RDWR) truncates the file (sets its size to 0)
  • O_CREAT tells open that if the file does not exist, it should be created
  • O_EXCL (shall be used only in conjunction with O_CREAT) makes open fail when the file exists
  • and at least a dozen of other flags

For example:

int fd1 = open("/tmp/foo", O_RDONLY);
if (fd1 == -1) perror("Opening /tmp/foo for reading failed");
 
int fd2 = open("/tmp/baz", O_WRONLY|O_APPEND);
if (fd2 == -1) perror("Opening /tmp/baz for appending (write-only) failed");
 
int fd3 = open("/tmp/bar", O_RDWR|O_CREAT|O_EXCL, 0600);
if (fd3 == -1) perror("Creating a new file /tmp/bar failed");
// if open succeeds, the file is open for reading and writing and has permissions of 0600

To close a file, POSIX defines:
Needs header:unistd.h int close(int filedes)
that closes a file number filedes.
On Linux, invoking close(fd) always closes fd, even if close returns -1.

Exercise 1 Open a file with hardcoded filename, read its contents and write it to standard output.

Exercise 2 Open a file specified as the first argument of your program, read its contents and write it to standard output.

Exercise 3 Open a file specified as the first argument of your program, read its contents and write it to standard output with line numbers (just like cat -n file).
memchr looks up a character (e.g., \n) in memory.

Exercise 4 Implement a program that checks if two files have the same contents.
memcmp compares two memory areas.

Exercise 5 Implement a program that works as paste [file]....
Hint: the dirty solution reads single character a time.

Fork

Forking

To create a new process, POSIX defines the fork function: Needs header:unistd.h pid_t fork()
fork creates a copy1) of the running process.
Upon success, fork returns the pid of the child process in the parent process, and the value of 0 in the the child process.
Fork may fail if the resource limits are exhausted, and in such case the expected -1 is returned.

There is a list of things that fork does not clone or that are reset for the child process upon fork. See POSIX standard or Linux manual on fork for details. Notice that:

  • all stack and heap memory is copied
  • the list of open files is copied
  • memory mappings are copied (unless one provides an explicit flag not to copy a mapping upon creating it)
  • signal handlers are copied
  • pending signals and timers are not copied
  • threads are not copied
    (but state of mutexes and values of semaphores are copied)
    forking in a multithreaded application is dangerous

Learning one's own pid

To learn its own process identifier, the process can execute Needs header: unistd.h pid_t getpid()
To learn the process identifier of its parent, the process can execute Needs header: unistd.h pid_t getppid()

Waiting for a child

The parent process shall care for its child processes once they terminate.
The programmer may either: 1) wait for the child to terminate, 2) set up a signal handler for SIGCHILD (it suffices to ignore the signal), 3) force the child out of the parent processes session (by using setsid() or double fork).

To wait for the termination of any child, one can call wait. To wait for the termination of a specific pid, one can call waitpid:
Needs header:
sys/wait.h
pid_t wait(int *stat_loc)
pid_t waitpid(pid_t pid, int *stat_loc, int options)

These functions return the process identifier of the child process which terminated.

The functions write the status of the termination to the memory pointed by stat_loc (provided it's not NULL) . The status provides information whether the process terminated normally (by calling exit or returning from main) and if it did, then which value it returned. See the documentation for a list of macros that extract the information.

If the pid in waitpid is positive, then it is considered the pid of the child to wait for. Non-positive values have special meanings. Among others, -1 waits for any child.

The options argument of waitpid is a combination of flags. The value of 0 simply waits until the pid terminates. The flag WNOHANG makes waitpid return immediately (and the return value indicates whether a child has terminated).

Exercises

Exercise 6 Write a program that:
    1. sleeps five seconds,
    2. prints a text with write,
    3. forks,
    4. prints another text with write,
    5. sleeps another five seconds.
Run the program and observe it in a live process viewer.
sleep(int sec) sleeps with second resolution. (The other POSIX sleep function is nanosleep; C standard includes now the equivalent thrd_sleep)
To keep an eye on the application, either use htop / top, or try watch -n 0.1 ps -lHC executable_name.

Exercise 7 Fork and print the result of fork, getpid and getppid.

Exercise 8 Fork. Print child in the child process and parent in the parent process.

Exercise 9 Fork. In the child process, exit immediately. In the parent, wait for input (e.g., do a read from 0 or invoke getchar).
Run the program and observe the zombie using htop / top / ps.

1) Nowadays forking is considered cheap, for it uses copy-on-write to reduce costs.
os_cp/open_fork.txt · Last modified: 2023/04/11 23:43 by jkonczak