(a) The memory layout of every process contains two important sections: the stack and the heap. Briefly describe the difference between these two.
(b) In the snippet below, in which sections do the values a, b, *b live. For a bonus, in which sections of memory do c and d live.
int foo(void)
{
int a = 1;
int *b = malloc(sizeof(*b));
static int c;
static int d = 5;
/* ... */
}
What causes the delivery of a SIGSEGV signal to a process?
In the macro below, why do we use a do/while loop?
#define mu_die(fmt, ...) \
do { \
fprintf(stderr, fmt "\n",##__VA_ARGS__); \
exit(1); \
} while (0)
What are the initial values of a, b[0], and c[0] in the code snippet below:
void foo(void)
{
int a;
char *b = malloc(10);
char *c = calloc(1, 10);
}
What is a file hole and name two ways to create one.
Given the st_size field of struct stat, what can we infer about the st_blocks field?
Give a limitation of a hard link and describe why a soft link overcomes this limitation.
What is the effect of the following statements?
fflush(fh);
fsync(fileno(fh));
Explain why the output of the following code differs depending on whether standard output is redirected to a terminal or to a disk file.
printf("If I had more time, \n");
write(STDOUT_FILENO, "I would have written you a shorter letter.\n", 43);
Suppose the file named /tmp/datafile exists (protected with mode 0666) and contains the following 26 bytes of character data:
abcdefghijklmnopqrstuvwxyz
What is the output of the following program?
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>
#include <unistd.h>
int
main(void)
{
int fd1, fd2, fd3;
char c1, c2, c3;
fd1 = open("/tmp/datafile", O_RDONLY);
fd2 = dup(fd1);
lseek(fd1, 2, SEEK_SET);
fd3 = open("/tmp/datafile", O_RDONLY);
read(fd1, &c1, 1);
read(fd2, &c2, 1);
read(fd3, &c3, 1);
printf("%c%c%c\n", c1, c2, c3);
close(fd1);
close(fd2);
close(fd3);
return 0;
}
How can we ensure that the following file is deleted when the process terminates, even if it terminates in an abnormal fashion (i.e., is killed by a signal). Explain.
fd = open("foo.tmp", O_RDWR| O_CREAT|O_EXCL, 0600);
Add a few lines of code to ensure that the following snippet of code is not interrupted by a SIGINT.
printf("start\n");
sleep(10);
printf("end\n");
What is the difference between SIGTERM and SIGKILL? Why is it generally better to first try to kill a process using SIGTERM?
The following signal handler is designed to reap terminated child processes. What are two problems with this signal handler? Modify the signal handler to fix these problems.
static void
sigchld_handler(int sig)
{
wait(NULL);
}
A signal handler often simply sets a global flag and then returns:
volatile sig_atomic_t flag;
static void
handler(int sig)
{
flag = 1;
}
Why is flag declared as sig_atomic_t? volatile?
If a process's parent terminates, how is the process reaped when it itself terminates?
Explain the difference between an orphaned process and a zombie process?
In many shells, the following syntax adds a value to the environment used to execute a program, without affecting the parent shell (or any subsequent commands):
NAME=value program
Both the putenv and the setenv C library functions add a variable to the environment. Recalling the bsh project, where (i.e., between which system calls) would bsh call putenv/setenv.
Suppose the file named /tmp/datafile exists (protected with mode 0666) and contains the following 26 bytes of character data:
abcdefghijklmnopqrstuvwxyz
Consider the following program:
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/wait.h>
#include <fcntl.h>
#include <unistd.h>
int
main(void)
{
int pfd[2],fd;
int i;
char ch;
pipe(pfd);
if (fork() == 0) {
close(pfd[1]);
while (read(pfd[0], &ch, 1) != 0)
write(STDOUT_FILENO, &ch, 1);
} else {
close(pfd[0]);
fd = open("/tmp/datafile", O_RDONLY);
for (i = 0; i < 5; i++) {
read(fd, &ch, 1);
write(pfd[1], &ch, 1);
}
wait(NULL);
}
return 0;
}
Select the statement which most accurately describes the process:
Many of the socket-related system calls take a struct sockaddr as an argument. However, in our programs, we never declare a variable of this type. Therefore, what is the purpose of the struct sockaddr type?
What is the purpose of the backlog parameter to the listen system call? Why shouldn't we specify this value as 0?
In socket programs, we often see code along the lines of:
signal(SIGPIPE, SIG_IGN);
What is the purpose of this line of code? Specifically, under what conditions would a networking process receive a SIGPIPE, and how does ignoring the SIGPIPE benefit the program?
Consider following client and server code:
/* client */
int
main(int argc, char *argv[])
{
int sk;
struct sockaddr_in server;
char *msg = "abcdefghijlkmnopqrstuvwxyz";
ssize_t n;
if (argc != 3)
mu_die("Usage: %s IP PORT", argv[0]);
sk = socket(AF_INET, SOCK_DGRAM, 0);
if (sk == -1)
mu_die_errno(errno, "socket");
mu_init_sockaddr_in(&server, argv[1], argv[2]);
n = sendto(sk, msg, 26, 0, (struct sockaddr *)&server, sizeof(server));
if (n == -1)
mu_die_errno(errno, "sendto #1");
n = sendto(sk, msg, 26, 0, (struct sockaddr *)&server, sizeof(server));
if (n == -1)
mu_die_errno(errno, "sendto #2");
close(sk);
return 0;
}
/* server */
#define BUF_SIZE 6
int
main(int argc, char *argv[])
{
struct sockaddr_in sa;
int sk;
int err;
char buf[BUF_SIZE] = { 0 };
ssize_t n;
if (argc != 3)
mu_die("Usage: %s IP PORT", argv[0]);
sk = socket(AF_INET, SOCK_DGRAM, 0);
if (sk == -1)
mu_die_errno(errno, "socket");
mu_init_sockaddr_in(&sa, argv[1], argv[2]);
err = bind(sk, (struct sockaddr *)&sa, sizeof(sa));
if (err == -1)
mu_die_errno(errno, "bind");
n = recvfrom(sk, buf, sizeof(buf) - 1, 0, NULL, NULL);
if (n == -1)
mu_die_errno(errno, "recvfrom #1");
printf("%s\n", buf);
memset(buf, 0x00, sizeof(buf));
n = recvfrom(sk, buf, sizeof(buf) - 1, 0, NULL, NULL);
if (n == -1)
mu_die_errno(errno, "recvfrom #2");
printf("%s\n", buf);
close(sk);
return 0;
}
Given the following command-lines, what does the server output?
./client 127.0.0.1 5140
./server 127.0.0.1 5140
Consider the program below:
static void *
thread_func(void *arg)
{
int v = *(int *)arg;
printf("%d\n", v);
return NULL;
}
int
main(void)
{
pthread_t t[4];
int i;
for (i = 0; i < NUM_THREADS; i++)
xpthread_create(&t[i], NULL, thread_func, &i);
for (i = 0; i < NUM_THREADS; i++)
xpthread_join(t[i], NULL);
return 0;
}
Which best describes the output:
fn main() {
let x = 5;
println!("{x}");
x = 6;
println!("{x}");
}
Consider the following code:
fn main() {
let i = 42;
let s = String::from("abc");
foo(i);
println!("{i}");
bar(s);
println!("{s}");
}
fn foo(j: i32) {
println!("{j}");
}
fn bar(t: String) {
println!("{t}");
}
Explain why the compiler is fine with printing i but complains about printing s. How would you modify the code so that it compiles?
fn main() {
let mut v = vec![10, 20, 30];
// TODO: for loop
}
Add a for loop that iterates over the vector and increments each element by 100.