Operating Systems Lab Exam 1 - Solutions.
Exercise 1: File Handling and Error Checking
Original Code
int main(int argc, char argv)
{
int f = open(argv, O_RDONLY), c, total;
while ((n == read(f, &c, 1)) > 0)
if (isalpha(c))
total++;
close(f);
printf("Found %uc alphanumeric elements in file!\n", total);
}
Solution
#include <fcntl.h> // For open()
#include <unistd.h> // For read() and close()
#include <ctype.h> // For isalnum()
#include <stdio.h> // For printf()
int main(int argc, char *argv[]) {
if (argc < 2) {
printf("Usage: %s <filename>\n", argv[0]);
return 1;
}
int f = open(argv[1], O_RDONLY);
if (f < 0) {
perror("Error opening file");
return 1;
}
int c, total = 0;
ssize_t n; // ssize_t is the correct type for the return value of read()
while ((n = read(f, &c, 1)) > 0) {
// Cast c to unsigned char before passing to isalnum
if (isalnum((unsigned char)c)) {
total++;
}
}
if (n < 0) {
perror("Error reading file");
close(f);
return 1;
}
close(f);
printf("Found %d alphanumeric elements in file!\n", total);
return 0;
}
Explanation
- Missing Headers: Added necessary headers for file operations and character checking.
- Command Line Arguments: Added check for correct number of arguments.
- Error Handling: Used
perror
for detailed error messages. - Proper Data Types: Used
ssize_t
forread()
return value. - Resource Management: Properly closed file descriptor.
- Character Checking: Cast to
unsigned char
forisalnum()
.
Exercise 2: Process Creation
Original Code
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include
int main(int argc, char **argv)
{
n = atoi(argv);
int pids[n];
for (i = 0; i < n; ++i) {
if ((pids[i] = fork()) < 0) {
print("Errors\n");
} else if (pids[i] > 0) {
printf("Child PID %d\n",getpid());
exit(i);
}
}
print("Done spawning %d subprocesses", n);
}
Solution
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/wait.h> // For wait()
int main(int argc, char **argv) {
if (argc != 2) {
perror("error need a number as arg");
return 1;
}
int n = atoi(argv[1]);
int pids[n];
for (int i = 0; i < n; i++) {
pids[i] = fork();
if (pids[i] < 0) {
printf("Errors\n");
} else if (pids[i] == 0) {
// Child process
exit(0);
}
}
// we need to wait for all of them.
for (int i = 0; i < n; i++) {
wait(NULL);
}
printf("Done spawning %d subprocesses\n", n);
return 0;
}
Explanation
- Header Inclusion: Added
sys/wait.h
forwait()
. - Argument Handling: Added check for correct number of arguments.
- Process Creation: Fixed
fork()
logic and error handling. - Synchronization: Added
wait()
calls to ensure parent waits for all children. - Proper Exits: Used
exit(0)
for child processes.
Exercise 3: Inter-Process Communication with Pipes
Original Code
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/wait.h>
int main(int argc, char **argv){
int pipe_fd[2];
int n = open(argv[1], O_WRONLY);
pid_t pid=fork();
if (pid<0){
perror("Issue with fork");
exit(EXIT_FAILURE);}
if (pid==0){
n = read(pipe_fd[0],buffer,sizeof(buffer));
printf("Total characters read: %d",n);
else{
char buffer[512];
read(fd,buffer,sizeof(buffer));
write(pipe_fd[1],buffer,sizeof(buffer));
wait(NULL);
}
Solution
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/wait.h>
#define BUFFER_SIZE 512
int main(int argc, char **argv) {
if (argc != 2) {
printf("error: please provide a filename\n");
return 1;
}
int pipe_fd[2];
if (pipe(pipe_fd) == -1) {
perror("pipe error");
return 1;
}
int fd = open(argv[1], O_RDONLY);
if (fd == -1) {
perror("open error");
return 1;
}
pid_t pid = fork();
if (pid < 0) {
perror("fork error");
exit(EXIT_FAILURE);
} else if (pid == 0) { // Child process
close(pipe_fd[1]);
char buffer[BUFFER_SIZE];
ssize_t total_bytes = 0;
ssize_t bytes_read;
while ((bytes_read = read(pipe_fd[0], buffer, BUFFER_SIZE)) > 0) {
total_bytes += bytes_read;
}
if (bytes_read == -1) {
perror("read child error");
exit(EXIT_FAILURE);
}
printf("Total characters read: %zd\n", total_bytes);
close(pipe_fd[0]);
exit(EXIT_SUCCESS);
} else { // Parent process
char buffer[BUFFER_SIZE];
close(pipe_fd[0]);
ssize_t bytes_read;
while ((bytes_read = read(fd, buffer, BUFFER_SIZE)) > 0) {
if (write(pipe_fd[1], buffer, bytes_read) == -1) {
perror("write parent error");
exit(EXIT_FAILURE);
}
}
if (bytes_read == -1) {
perror("read parent error");
exit(EXIT_FAILURE);
}
close(pipe_fd[1]);
close(fd);
wait(NULL);
}
return 0;
}
Explanation
- Pipe Creation: Added proper pipe creation and error handling.
- File Handling: Added proper file opening and error checking.
- Fork Handling: Fixed fork logic and added proper error handling.
- IPC: Implemented proper read/write operations on pipe.
- Resource Management: Properly closed file descriptors.
- Synchronization: Added
wait()
for proper process synchronization.
Exercise 4: Process Memory and Fork
Original Code
#include <sys/types.h>
#include <stdio.h>
#include <unistd.h>
#define SIZE 5
int nums[SIZE] = {0,1,2,3,4};
int main()
{
int i;
pid t pid;
pid = fork();
if (pid == 0) {
for (i = 0; i < SIZE; i++) {
nums[i] *= -i;
printf("%d ",nums[i]);LINE X
}
}
else if (pid > 0) {
for (i = 0; i < SIZE; i++)
printf("PARENT: %d ",nums[i]); LINE Y
}
return 0;
}
Solution
#include <sys/types.h>
#include <stdio.h>
#include <unistd.h>
#define SIZE 5
int nums[SIZE] = {0,1,2,3,4};
int main()
{
int i;
pid_t pid;
pid = fork();
if (pid == 0) {
for (i = 0; i < SIZE; i++) {
nums[i] *= -i;
printf("%d ",nums[i]); /* LINE X */
}
}
else if (pid > 0) {
for (i = 0; i < SIZE; i++)
printf("PARENT: %d ",nums[i]); /* LINE Y */
}
return 0;
}
Explanation
- Process Creation: Uses
fork()
to create a child process. - Memory Management: Demonstrates that changes in child process do not affect parent process memory.
- Output:
- Child process prints:
0 -1 -4 -9 -16
- Parent process prints:
0 1 2 3 4
- Child process prints:
Exercise 5: File Opening and Permissions
Original Code
#include <unistd.h>
#include <fcntl.h>
int main(int argc, char argv)
{
int f = open(argv O_RDONLY);
printf("Opened file");
return 0;
}
Solution
#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>
int main(int argc, char **argv){
if (argc != 2) {
perror("arg err");
}
int f = open(argv[1], O_RDONLY);
if (f != -1) {
printf("Opened file");
}else {
perror("err");
}
return 0;
}
Explanation
- Argument Handling: Added check for correct number of arguments.
- Error Handling: Used
perror
for detailed error messages. - File Opening: Added proper file opening with
O_RDONLY
flag. - Resource Management: Properly checked file descriptor.
Exercise 6: File Permissions and Modes
Original Code
#include <unistd.h>
#include <stdio.h>
#include <fcntl.h>
#include <sys/stat.h>
int main(int argc, char **argv)
{
int file = open(argv, O_WRONLY);
struct stat fileStat;
if(lstat(file,&stat) < 0)
{
return 1;
}
printf( (S_ISDIR(stat.st_mode)) ? "d" : (S_ISLNK(stat.st_mode)) ? "l" : "-");
printf( (stat.st_mode & S_IRUSR) ? "w" : "-");
printf( (stat.st_mode & S_IWUSR) ? "x" : "-");
printf( (stat.st_mode & S_IXUSR) ? "r" : "-");
printf( (stat.st_mode & S_IRGRP) ? "w" : "-");
printf( (stat.st_mode & S_IWGRP) ? "x" : "-");
printf( (stat.st_mode & S_IXGRP) ? "r" : "-");
printf( (stat.st_mode & S_IROTH) ? "w" : "-");
printf( (stat.st_mode & S_IWOTH) ? "x" : "-");
printf( (stat.st_mode & S_IXOTH) ? "r" : "-");
close(file);
return 0;
}
Solution
#include <unistd.h>
#include <stdio.h>
#include <fcntl.h>
#include <sys/stat.h>
int main(int argc, char **argv) {
if (argc < 2) {
fprintf(stderr, "Usage: %s <file>\n", argv[0]);
return 1;
}
const char *filename = argv[1];
/*
mode_t permissions = S_IRUSR | S_IRGRP | S_IROTH | // Read permissions for all
S_IXGRP | S_IXOTH | // Execute permissions for group and others
S_IWOTH; // Write permission for others
// or using octa:
*/
//mode_t permissions = 0457;
int permissions = 0457;
// Open the file with O_CREAT and the desired permissions
int file = open(filename, O_WRONLY | O_CREAT, permissions);
if (file < 0) {
perror("open");
return 1;
}
close(file);
// Explicitly set the permissions using chmod to override umask
if (chmod(filename, permissions) < 0) {
perror("chmod");
}
// lstat for file metadata
struct stat fileStat;
if (lstat(filename, &fileStat) < 0) {
perror("lstat");
}
// File type
// it is fileStat.st_mode
printf((S_ISDIR(fileStat.st_mode)) ? "d" :
(S_ISLNK(fileStat.st_mode)) ? "l" : "-");
// Owner permissions
printf((fileStat.st_mode & S_IRUSR) ? "r" : "-");
printf((fileStat.st_mode & S_IWUSR) ? "w" : "-");
printf((fileStat.st_mode & S_IXUSR) ? "x" : "-");
// Group permissions
printf((fileStat.st_mode & S_IRGRP) ? "r" : "-");
printf((fileStat.st_mode & S_IWGRP) ? "w" : "-");
printf((fileStat.st_mode & S_IXGRP) ? "x" : "-");
// Other permissions
printf((fileStat.st_mode & S_IROTH) ? "r" : "-");
printf((fileStat.st_mode & S_IWOTH) ? "w" : "-");
printf((fileStat.st_mode & S_IXOTH) ? "x" : "-");
printf("\n");
return 0;
}
Explanation
Permission Setting: Used
chmod()
to set permissions.Metadata Retrieval: Used
lstat()
to get file metadata.Trap:
Because of system
umask
which is default 0022 (the write for other usrs won't be applied.) to fix this we use chmod to "force it" and apply 0002 that is needed.
Exercise 7: Process Spawn Based on Executable Name
Original Code
#include <stdlib.h>
#include <stdio.h>
#include <ctype.h>
#include <sys/wait.h>
#include <unistd.h>
#include <string.h>
// Function to check if a character is a vowel
int isvowel(char ch) {
char lower = tolower(ch);
return (lower == 'a' || lower == 'e' || lower == 'i' || lower == 'o' || lower == 'u');
}
// Function to check if a character is a consonant
int isconsonant(char ch) {
char lower = tolower(ch);
return (lower >= 'a' && lower <= 'z' && !isvowel(lower));
}
// Function to extract the executable name from the full path
char *get_executable_name(char *path) {
char *name = strrchr(path, '/'); // Find the last '/' in the path
return (name == NULL) ? path : name + 1; // Return the name after the last '/'
}
int main(int argc, char **argv) {
if (argc < 1) {
fprintf(stderr, "Error: No executable name provided.\n");
return 1;
}
char *path = argv[0];
char *name = get_executable_name(path); // Extract the executable name
int count_v = 0, count_c = 0;
// Check if the executable name starts with a vowel
if (isvowel(name[0])) {
pid_t c1 = fork();
if (c1 == 0) {
// Child process: Count vowels
for (int i = 0; i < strlen(name); i++) {
if (isvowel(name[i])) {
count_v++;
}
}
printf("Number of vowels: %d\n", count_v);
return 0;
} else if (c1 == -1) {
perror("Error creating child process");
return 2;
} else {
// Parent process
wait(NULL);
printf("Testing executable name starting with a vowel: %s\n", path);
}
} else {
// Executable name does not start with a vowel: Create 2 child processes
pid_t c2 = fork();
if (c2 == 0) {
// First child process: Count consonants
for (int i = 0; i < strlen(name); i++) {
if (isconsonant(name[i])) {
count_c++;
}
}
printf("Number of consonants: %d\n", count_c);
return 0;
} else if (c2 == -1) {
perror("Error creating first child process");
return 3;
} else {
// Parent process creates the second child
pid_t c3 = fork();
if (c3 == 0) {
// Second child process: Count consonants
for (int i = 0; i < strlen(name); i++) {
if (isconsonant(name[i])) {
count_c++;
}
}
printf("Number of consonants: %d\n", count_c);
return 0;
} else if (c3 == -1) {
perror("Error creating second child process");
return 4;
} else {
// Parent process waits for both children
wait(NULL);
wait(NULL);
printf("Testing executable name not starting with a vowel: %s\n", path);
}
}
}
return 0;
}
Explanation
- Executable Name Check: Checks if executable name starts with a vowel.
- Process Creation: Creates 1 child if name starts with vowel, otherwise creates 2 children.
- Counting Logic: Counts vowels or consonants based on process creation.
- Synchronization: Uses
wait()
to ensure proper process termination.
Exercise 8: Reading from Standard Input
Original Code
#include <stdio.h>
#include <fcntl.h>
#include <unistd.h>
int main()
{
unsigned int c;
read(STDIN_FILENO, &c, 4);
printf("Read character %x", c);
}
Explanation
- Input Handling: Reads 4 bytes from standard input.
- Endianness: System uses little-endian, so bytes are reversed.
- Output: Prints the hexadecimal representation of the read value.
Exercise 9: Signal Handling
Original Code
#include <stdio.h>
#include <unistd.h>
#include <signal.h>
#include <stdlib.h>
void handle_sigstp(int signum)
{
sa.sa_handler = &handle_sigstp;
sa.sa_flags = SA_RESTART;
sigaction(SIGSTOP, &sa, NULL);
}
int main()
{
printf("STOP signal received\n");
return 0;
}
Solution
#include <stdio.h>
#include <unistd.h>
#include <signal.h>
#include <stdlib.h>
void handle_sigstp(int signum){
printf("STOP sig recv\n");
}
int main(){
struct sigaction sa;
sa.sa_handler = &handle_sigstp;
sa.sa_flags = SA_RESTART;
sigemptyset(&sa.sa_mask);
if (sigaction(SIGTSTP, &sa, NULL) == -1) {
perror("error sigaction");
return 1;
}
while (1) {
pause();
}
return 0;
}
Explanation
- Signal Handling: Registers handler for SIGTSTP.
- Signal Masking: Uses
sigemptyset()
to clear signal mask. - Process Suspension: Uses
pause()
to wait for signals.
Exercise 10: Thread-Based File Reading
Original Code
// Structure to hold thread-specific data
typedef struct
{
int start;
int end;
char *filename;
} thread_info;
void *countCharacters(void *threadarg)
{
thread_info *threadData;
int start, end;
FILE *file;
char ch;
threadData = (thread_info *)threadarg;
start = threadData->start;
file = fopen(threadData->filename, "r");
if (!file) {
perror("Error opening file");
pthread_exit(NULL);
}
fseek(file, start, SEEK_SET);
fclose(file);
totalCount++
pthread_exit(NULL);
}
Solution
#include <stdio.h>
#include <pthread.h>
#include <stdlib.h>
#include <errno.h>
// long is used for file chunking.
typedef struct {
long start;
long end;
const char *filename;
long count; // thread individual count
} thread_info;
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
long totalCount = 0; // global for all threads accesable..
void *countCharacters(void *threadarg) {
thread_info *threadData = (thread_info *)threadarg;
FILE *file;
int ch;
long localCount = 0;
file = fopen(threadData->filename, "r");
if (!file) {
perror("Error opening file");
pthread_exit(NULL);
}
if (fseek(file, threadData->start, SEEK_SET) != 0) {
perror("Error seeking in file");
fclose(file);
pthread_exit(NULL);
}
// iterating through chunks.
while (ftell(file) < threadData->end && (ch = fgetc(file)) != EOF) {
localCount++;
}
threadData->count = localCount;
// mutex are needed here to counter race conditions.
pthread_mutex_lock(&mutex);
totalCount += localCount;
pthread_mutex_unlock(&mutex);
fclose(file);
pthread_exit(NULL);
}
Explanation
- Thread Creation: Creates threads to read different parts of a file.
- File Handling: Opens and seeks to specific positions in file.
- Synchronization: Uses mutex to protect shared variable
totalCount
. - Race Conditions: Avoids race conditions with proper locking.
To solve exercises you need to understand the following concepts:
General Concepts:
System Calls and Library Functions:
fork()
,open()
,read()
,write()
,close()
,wait()
,pipe()
,fseek()
,fopen()
,fgetc()
,fclose()
,sigaction()
,pause()
).
Error Handling:
- Using
perror()
- Since the proff doesn't care to see this. you can just use
printf()
andreturn
orexit
- Using
Process Management:
fork() and wait() logic
Inter-process (pipes):
- fd and pipes, read, write to buffer, and closing pipes.
File Management:
- Opening files with appropriate flags (
O_RDONLY, O_WRONLY
). - or OCTAL representation.
- Understanding file permissions and how to set them using
chmod()
. - keep in mind the restrictions by umask
- Opening files with appropriate flags (
Signal Handling:
- Struct Signals.
- Setting up signal handlers using `sigaction().
- Understanding signal types (e.g.,
SIGTSTP
) and their handling. - SIGSTOP and SIGTSTP (force termination and pause)
Thread Programming:
pthread_create()
.- mutexes and semaphores.
- advanced pointers for casting and using structs for passing information with threads to working function.
Character and String Operations:
isalnum().
<ctype.h>
Endianness and Data Representation:
- Understanding how data is stored in memory (little-endian vs. big-endian).
- Handling byte order conversions.
concepts and Traps per exercise:
Exercise 1:
- File handling with
open()
,read()
, andclose()
. - TRAP was isalpha() which checks for alphabetic only!
- request was alphanumeric which is:
isalnum()
to check for alphanumeric characters.
- File handling with
Exercise 2:
fork()
.- Synchronizing with
wait()
.
Exercise 3:
pipe()
.- read and write to pipes
Exercise 4:
- manual compile and see results.
- memory separation in parent and child processes.
Exercise 5:
- Opening files with appropriate permissions.
- Basic error handling for file operations.
- ALL PERMISION FLAGS
Exercise 6:
- file permissions using
chmod()
. - Retrieving file metadata with
lstat()
. - Displaying file type and permissions.
- the umask trap where you can't add permisions, use
chmod()
.
- file permissions using
Exercise 7:
- pass trough args
- checking if a character is a vowel or consonant. you have to write your own functions for this.
- A LOT of logic, and you need to keep track of processes and logic for consonants and vowels.
Exercise 8:
read()
.- understanding endianness and data representation.
- ASCII / hexa
- you can print a lot in other exercises fields, if you have time.
Exercise 9:
- Handling signals with `sigaction().
- Handling signals with STRUCTS!
- Using
pause()
to wait for signals. - Setting up signal handlers.
- Trap: was the SIGSTOP and SIGTSTP (which are not the same thing.)
Exercise 10:
- Using threads to read different parts of a file.
- file chunking with struct info carry threads.
- Synchronizing threads with mutexes.
- thread-specific data structures, casting void pointers for struct info carry threads.
END OF exam