AOS paper solution
AOS paper solution
a) True or False Justify: “The kernel is a separate set of process that run in parallel to user
processes.”
Answer:-
False.
Justification:
The kernel is not a "separate set of processes" that runs parallel to user processes. Instead, the
kernel is the core part of an operating system that operates in kernel mode. It acts as an
intermediary between the hardware and the user processes.
The kernel does not run as a process but rather as a privileged mode of execution within the
CPU. It provides essential services such as process management, memory management, I/O
handling, and device control. User processes request services from the kernel using system calls,
but the kernel does not function as a standalone set of parallel processes. It manages the
execution of all processes and system resources rather than running as parallel processes itself.
b) What are the 4 different conditions for the pid argument of kill system call?
Answer:-
The kill system call in UNIX-like operating systems is used to send signals to processes. The
behavior of the kill system call depends on the value of the pid (process ID) argument. The
four main conditions for the pid argument are:
Each of these conditions allows the kill system call to be used flexibly to target individual
processes, groups of processes, or a broad set of processes.
Answer:-
The wait and waitpid system calls in UNIX-like operating systems are used by a parent process
to wait for its child process(es) to change state, typically to terminate. Here are the key
differences between the two:
1. Functionality
wait:
o The wait system call waits for any one of the child processes to change state
(e.g., terminate).
o It does not allow the parent to specify which child process to wait for.
waitpid:
o The waitpid system call is more versatile and allows the parent process to wait
for a specific child process (identified by its PID) or a subset of child processes,
based on the pid parameter.
wait:
o Lacks control; it waits for the first child process that changes state.
waitpid:
o Offers more control. By passing specific arguments, you can wait for:
A specific child process (using its PID).
Any child in the same process group.
Any child process (similar to wait).
wait:
o Always blocks the parent process until a child changes state.
waitpid:
o Can operate in non-blocking mode if the WNOHANG option is specified. In this
case, it returns immediately if no child has changed state.
wait:
o Simpler interface; does not take any arguments other than a pointer to store the
exit status of the child.
o Example: pid = wait(&status);
waitpid:
o More complex; takes multiple arguments:
pid: Specifies the PID or group of processes to wait for.
options: Modifies behavior (e.g., WNOHANG for non-blocking).
o Example: pid = waitpid(target_pid, &status, WNOHANG);
5. Use Case
wait:
o Suitable for simple scenarios where the parent process has multiple children but
does not care which one terminates.
waitpid:
o Used when the parent needs to:
Wait for a specific child process.
Implement non-blocking behavior.
Handle more complex process management scenarios.
Summary Table
d) If we execute iseek(fd, 0, 2) then what will be the new file byte offset?
Answer:-
The lseek system call (not iseek, which is likely a typo) is used to reposition the file offset of
an open file descriptor. The general syntax for lseek is:
c
Copy code
off_t lseek(int fd, off_t offset, int whence);
Here:
Analysis of lseek(fd, 0, 2)
1. Parameters:
o fd: The file descriptor for the open file.
o offset: 0, meaning no additional offset is applied.
o whence: 2 (which corresponds to SEEK_END), indicating the offset is set relative to
the end of the file.
2. Effect:
o The file offset is set to the end of the file plus 0.
o The new file byte offset will be equal to the size of the file in bytes.
3. Result:
o If the size of the file is N bytes, the new file byte offset will be N.
o For example:
If the file is 100 bytes, lseek(fd, 0, 2) sets the offset to 100.
Answer:-
Definition:
A broken link in an operating system occurs when a symbolic link (soft link) points to a target
file or directory that does not exist or is inaccessible.
Explanation:
Symbolic links are special types of files that serve as pointers to another file or directory. If the
target of a symbolic link is deleted, renamed, or moved, the link becomes "broken" because it no
longer points to a valid location.
For example:
1. Target File Deletion: The target file or directory is removed from the file system.
2. Target Relocation: The target is moved to a different location without updating the
symbolic link.
3. Permission Issues: The target exists, but the symbolic link user does not have
permissions to access it.
4. Unmounted File Systems: If the target resides on a file system that is no longer
mounted, the symbolic link becomes invalid.
ls -l (shows links that point to nonexistent targets with errors like "No such file or
directory").
find /path -xtype l (locates broken symbolic links).
1. Inconsistency in File Systems: Broken links can lead to user confusion and errors when
navigating the file system.
2. Software Issues: Programs relying on symbolic links might fail if those links are broken.
3. System Maintenance Challenges: Accumulation of broken links can clutter the file
system and degrade usability.
Solutions:
1. Remove Broken Links: Use commands like rm to delete invalid links.
2. Update Symbolic Links: Relink to the correct target using ln -sf.
3. Automated Cleanup: Scripts or tools like fsck (file system consistency check) can
identify and clean broken links.
Process ID (PID): It is a unique identifier assigned to each process when it is created by the
operating system. The PID helps the OS track and manage running processes.
Parent Process ID (PPID): Every process, except the initial system process, has a parent process.
The PPID is the identifier of the process that created the current process. When a process
creates another (usually via the fork() system call), the child process gets a PID and the parent
process gets the child's PPID.
In Unix-like operating systems (e.g., Linux), you can use system calls to retrieve the PID and
PPID.
Example in C:
c
Copy code
#include <stdio.h>
#include <unistd.h> // for getpid() and getppid()
int main() {
pid_t pid = getpid(); // Get the PID of the current process
pid_t ppid = getppid(); // Get the PPID of the current process
return 0;
}
Explanation:
Answer:-
What is a Process?
A process is a program that is in execution. It consists of the program code, current activity, and
system resources allocated to it (like memory, registers, etc.). A process is an active entity,
unlike a program, which is a passive set of instructions stored in a file. Processes are
fundamental to the functioning of an operating system and are responsible for executing tasks
and performing computations.
Components of a Process:
Process Control Block (PCB): Contains all the information about a process, such as its state,
program counter, CPU registers, memory management information, and accounting
information.
Program Code: The code that the process is executing.
Memory: The data and stack used by the process.
The state transition diagram of a process illustrates the different states that a process can be in
during its lifetime and the transitions between those states. Here’s the typical state transition
diagram for a process in most operating systems:
sql
Copy code
+------------------------+
| New |
| (Process is being |
| created) |
+-----------+------------+
|
v
+------------------------+
| Ready |
| (Process is waiting |
| for CPU allocation) |
+-----------+------------+
|
v
+------------------------+ +------------------------+
| Running | | Waiting |
| (Process is executing |<------>| (Process is waiting |
| on CPU) | | for some event to
occur)|
+-----------+------------+ +------------------------+
|
v
+------------------------+
| Terminated |
| (Process has finished |
| execution) |
+------------------------+
1. New:
o A process is in the New state when it is being created. In this state, the operating system
allocates resources for the process, and the process control block (PCB) is initialized.
2. Ready:
o In the Ready state, the process is loaded into memory and ready to be executed, but it
is waiting for the CPU to become available. Multiple processes can be in the ready state,
and the operating system’s scheduler decides which process should run next.
3. Running:
o The process moves to the Running state when it is assigned to a CPU core. In this state,
the process is actively being executed by the CPU. The program counter and registers
are updated while the process runs.
New → Ready: A new process, after being created, moves to the ready state when it is loaded
into memory and prepared for execution.
Ready → Running: The operating system’s scheduler picks a process from the ready queue and
assigns the CPU to it, moving it to the running state.
Running → Waiting: A process may move to the waiting state if it needs to wait for an I/O
operation or some other event to occur.
Waiting → Ready: Once the event the process was waiting for is completed (e.g., I/O operation
finishes), the process moves back to the ready state to wait for CPU time again.
Running → Terminated: Once a process finishes executing or is explicitly terminated by the
operating system or another process, it transitions to the terminated state.
Ready → Terminated: A process may directly move from the ready state to terminated if the
operating system kills the process before it starts running.
In the Unix operating system, demand paging is a technique used to load pages into memory
only when they are required by the running process, rather than loading the entire process into
memory at once. Unix uses several data structures to efficiently manage this process, which are
essential for virtual memory management, page fault handling, and ensuring that only the
required portions of a program are in memory at any given time.
Here are three important data structures used in demand paging in a Unix-like
operating system:
1. Page Table
The Page Table is one of the most fundamental data structures in demand paging. It maintains
the mapping between a process's virtual address space and physical memory.
Function in Unix:
The page table maps virtual pages to physical frames in memory. Each process in the Unix
operating system has its own page table.
It keeps track of which virtual pages are currently loaded in physical memory and which are
swapped out to disk.
The page table entry (PTE) contains not only the physical frame number but also additional
control bits (valid bit, dirty bit, reference bit, etc.).
Components of a Page Table Entry (PTE):
Frame Number: The physical frame in memory where the page is stored.
Valid Bit: Indicates whether the page is currently in memory (1) or on disk (0).
Dirty Bit: Set if the page has been modified and needs to be written back to disk before
replacement.
Protection Bits: Control the permissions of the page (read, write, execute).
Example:
markdown
Copy code
Page Table:
Page Number | Frame Number | Valid Bit | Dirty Bit | Protection Bits
---------------------------------------------------------------
0 | 2 | 1 | 0 | RW
1 | 4 | 1 | 1 | R
2 | -1 | 0 | - | -
3 | -1 | 0 | - | -
In this example:
The Swap Space (or Swap File) is a special area on disk used to store pages that are swapped
out of memory when the system runs low on physical memory. The swap space allows the
operating system to free up RAM by moving some pages of a process to the disk.
Function in Unix:
When a process accesses a page that is not currently in memory (a page fault occurs), the
operating system will look at the page table to check if the page is on disk. If it is, the OS will
swap the page into memory from the swap space.
The swap space is organized as a series of blocks or pages, and the Swap Table keeps track of
which pages of memory are currently stored on disk.
Swap Table:
A Swap Table in Unix maps the virtual pages that are swapped out to disk. Each entry in the
swap table contains information about the location of a page on the disk, allowing the system to
quickly retrieve the data when needed.
Components of the Swap Table:
In this example:
Page 2 is stored in swap block 1024 and has been referenced 3 times.
Page 3 is stored in swap block 2048 and has been referenced once.
The Translation Lookaside Buffer (TLB) is a cache used to speed up the virtual-to-physical
address translation process. When a process accesses memory, the system first checks the TLB
before accessing the page table. The TLB stores a small subset of the page table entries (PTEs)
for recently used virtual pages.
Function in Unix:
The TLB reduces the time required to translate virtual addresses into physical addresses by
storing the most recently accessed mappings.
If a virtual page's address translation is found in the TLB (a TLB hit), the physical address is
retrieved quickly.
If the translation is not in the TLB (a TLB miss), the operating system needs to consult the page
table.
Virtual Page Number: The virtual page that is currently cached in the TLB.
Physical Frame Number: The corresponding physical frame to which the virtual page maps.
Valid Bit: Indicates whether the TLB entry is valid.
Protection Bits: Specifies access rights (read, write, execute) for the cached page.
TLB Example:
markdown
Copy code
TLB Cache:
Virtual Page | Physical Frame | Valid Bit | Protection Bits
--------------------------------------------------------
1 | 4 | 1 | RW
2 | 7 | 1 | R
4 | 2 | 1 | RW
5 | 8 | 1 | R
In this case:
When accessing virtual page 1, the system can quickly translate it to physical frame 4 by
checking the TLB.
If page 1 is not in the TLB, the system will check the page table.
Answer:-
i) alarm()
The alarm() system call is used to set a timer to send a signal to the calling process after a
specified amount of time.
Syntax:
c
Copy code
unsigned int alarm(unsigned int seconds);
Parameters:
seconds: The number of seconds after which the system will send the SIGALRM signal to the
calling process.
Return Value:
Returns the number of seconds remaining until any previously set alarm was triggered. If no
alarm was previously set, it returns 0.
Explanation:
This system call is used to set up an alarm that will send the SIGALRM signal to the process after
the specified number of seconds.
It can be used for time-based operations, such as creating timeouts.
ii) kill()
The kill() system call is used to send a signal to a process. The signal could be for various
purposes (e.g., termination, pause, resume).
Syntax:
c
Copy code
int kill(pid_t pid, int sig);
Parameters:
pid: The process ID (PID) of the process to which the signal will be sent. It can also be used with
special values:
o pid > 0: Send signal to the process with the specified PID.
o pid == 0: Send the signal to all processes in the same process group as the caller.
o pid == -1: Send the signal to all processes except for the system processes.
o pid < -1: Send the signal to all processes in the process group specified by pid.
sig: The signal to be sent (e.g., SIGKILL, SIGSTOP, SIGTERM, etc.).
Return Value:
Explanation:
This system call allows a process to send signals to other processes, typically used for process
management (e.g., killing or stopping a process).
iii) sbrk()
The sbrk() system call is used to change the data space (heap) size of the calling process. It
adjusts the program's break, which defines the end of the process's data segment.
Syntax:
c
Copy code
void *sbrk(intptr_t increment);
Parameters:
increment: The number of bytes to increase or decrease the program's data space. A positive
value increases the size of the data segment, and a negative value decreases it.
Return Value:
Returns the previous end of the data segment (the previous value of the program's break).
Explanation:
sbrk() is used to allocate more memory to the heap by adjusting the program's break point. It
is generally used for dynamic memory management, although modern systems use malloc()
instead.
It's often replaced by mmap() in modern systems for memory allocation due to limitations and
potential issues with using sbrk().
iv) execl()
The execl() system call is part of the exec family, which replaces the current process image
with a new program.
Syntax:
c
Copy code
int execl(const char *path, const char *arg, ..., (char *)NULL);
Parameters:
Return Value:
On success, execl() does not return because the current process is replaced by the new
program.
On failure, it returns -1 and sets errno to indicate the error.
Explanation:
execl() replaces the current process image with a new one, specified by the path and
arguments.
It is used when you want to execute a different program within the current process (such as
running a shell command from a C program).
v) fchmod()
The fchmod() system call changes the file permissions of a file based on a file descriptor.
Syntax:
c
Copy code
int fchmod(int fd, mode_t mode);
Parameters:
fd: The file descriptor referring to the file whose permissions are to be changed.
mode: The new file permission bits, represented as an octal value. These specify the read, write,
and execute permissions for the file owner, group, and others (e.g., 0644 for read-write for the
owner, and read-only for others).
Return Value:
Returns 0 on success.
Returns -1 on failure, and errno is set to indicate the error.
Explanation:
fchmod() allows changing the permissions of a file that has already been opened, identified by
the file descriptor fd.
It provides a way to modify a file's access control while the file is in use, without needing to
close and reopen it.
Answer:-
Explanation:
1. Argument Check:
c
Copy code
if (argc != 2) exit(1);
o The program expects exactly one command-line argument (the file name). If the
user provides an incorrect number of arguments, the program exits immediately
without doing anything.
2. File Opening:
c
Copy code
fd = open(argv[1], O_RDONLY);
if (fd == -1)
o The program attempts to open the file provided as the first argument (argv[1]) in
read-only mode (O_RDONLY). If the file can't be opened (e.g., the file does not
exist or there are permission issues), the program will print an error message and
exit.
3. Reading and Seeking:
c
Copy code
while (skval = read(fd, &c, 1))
{
printf("char %c\n", c);
skval = lseek(fd, 1023L, SEEK_CUR);
printf("new seek val %d\n", skval);
}
o The program enters a loop where it reads 1 byte at a time from the file using the
read() system call. It prints the character read using printf().
o After reading each byte, the program performs a seek operation using the
lseek() system call:
lseek(fd, 1023L, SEEK_CUR) moves the file pointer 1023 bytes
forward from the current position (SEEK_CUR).
The new position of the file pointer is printed.
4. Exiting the Loop:
o The read() function returns the number of bytes actually read. If it returns 0, it
indicates end-of-file (EOF) has been reached, and the loop will terminate.
o If an error occurs during the read, read() returns -1, and the loop will also stop.
5. Closing the File:
c
Copy code
close(fd);
o After completing the reading and seeking operations, the file is closed using
close().
Expected Behavior:
1. Program Starts:
o The program checks if the correct number of arguments is passed. If not, it exits
immediately.
2. Opening the File:
o If the file exists and is accessible, it is opened in read-only mode. If the file cannot
be opened, the program prints an error message and exits.
3. Reading and Printing Characters:
o The program will continuously read 1 byte from the file and print the
corresponding character until it reaches the end of the file.
o After each character is printed, the file pointer is moved forward by 1023 bytes
due to the lseek() call.
4. Seek Operation:
o Each time the file pointer is moved (due to lseek()), the new seek position is
printed. For example, after reading the first byte, the pointer will be moved 1023
bytes ahead from its current position, and the new position will be printed.
5. End of File:
o Once the file is fully read, the read() function will return 0, which causes the
loop to exit. The program will then close the file.
Potential Issues:
Sample Output:
Assume the input file contains the following data: Hello World!
arduino
Copy code
char H
new seek val 1024
arduino
Copy code
char e
new seek val 2047
The program continues reading and seeking through the file, moving the file pointer 1023
bytes forward each time, printing each character, and printing the new seek value.
3. End of File:
o Once the file is fully read, the loop will exit, and the program will finish.
b) Write a C program to prints the type of file for each command line argument.
Answer:-
#include <stdio.h>
#include <stdlib.h>
#include <sys/stat.h>
#include <unistd.h>
// Loop through each command line argument and check file type
for (int i = 1; i < argc; i++) {
print_file_type(argv[i]);
}
return 0;
}
Explanation:
Sample Output:
bash
Copy code
./filetype file1.txt directory symlink
csharp
Copy code
'file1.txt' is a regular file.
'directory' is a directory.
'symlink' is a symbolic link.
In Unix-like operating systems, pipes are a method used for inter-process communication
(IPC). They allow data to be transferred from one process to another. A pipe acts as a conduit
between processes, enabling them to send and receive data in a unidirectional flow. Pipes are
often used to chain commands together in the shell, where the output of one command becomes
the input to another.
Types of Pipes:
c
Copy code
int pipefd[2];
if (pipe(pipefd) == -1) {
perror("pipe");
exit(1);
}
o In this case, pipefd[0] is the read end, and pipefd[1] is the write end of the pipe.
Behavior:
o An unnamed pipe is typically used between related processes, such as a parent and its
child processes.
o Data written to the write end (pipefd[1]) is available to be read from the read end
(pipefd[0]).
o The pipe is typically closed after use and cannot be shared across unrelated processes.
Usage: Unnamed pipes are commonly used in shell pipelines (e.g., ls | grep 'file'),
where the output of one command is piped into the next.
Limitations:
o Unnamed pipes are temporary and only exist as long as the processes sharing the pipe
are running.
o They can only be used between related processes (e.g., parent-child processes).
o The data is transferred in one direction (from the writer to the reader).
Definition: Named pipes (also known as FIFOs, which stands for First In, First Out)
are a type of pipe that can be used for communication between unrelated processes.
Unlike unnamed pipes, named pipes exist as a special type of file in the file system.
Creation: Named pipes are created using the mkfifo() system call, or they can be
created using the mknod() system call. A FIFO behaves like a pipe but can be identified
by a name (a path) in the file system.
o Example:
c
Copy code
if (mkfifo("/tmp/myfifo", 0666) == -1) {
perror("mkfifo");
exit(1);
}
Behavior:
o Named pipes have a persistent existence and can be accessed by unrelated processes
that know the name of the pipe.
o The data is written to the write end of the pipe and can be read from the read end.
o The reader blocks if there is no data, and the writer blocks if the pipe's buffer is full.
Usage: Named pipes can be used to exchange data between processes running on the
same machine, even if the processes are unrelated. A process can open a named pipe,
write to it, and another process (whether a parent or unrelated process) can read from it.
o Example of shell usage: echo "Hello" > /tmp/myfifo
Advantages:
o They allow communication between unrelated processes.
o Named pipes persist as files in the file system, allowing them to be referenced by their
names.
Limitations:
o Named pipes are typically local to the system where they are created and cannot be
used to communicate across different machines.
o The performance might not be as fast as unnamed pipes, especially when used for
frequent, large data transfers.
Creation Created using pipe() call Created using mkfifo() call or mknod()
Used for communication between related Used for communication between unrelated
Usage
processes processes
Lifetime Exists only while processes are running Exists until explicitly removed by the user
Data
One-way communication (unidirectional) One-way communication (unidirectional)
Direction
Can only be accessed by related processes Can be accessed by any process with the right
Access
(e.g., parent-child) permissions
Blocks the writer or reader if the pipe is Same as unnamed pipes, but can be accessed
Blocking
full or empty by unrelated processes
Example:
c
Copy code
#include <stdio.h>
#include <unistd.h>
int main() {
int pipefd[2];
pid_t pid;
char buf[20];
pipe(pipefd); // Create the pipe
pid = fork();
if (pid == 0) {
// Child process
close(pipefd[0]); // Close the read end
write(pipefd[1], "Hello from child", 17); // Write data to pipe
close(pipefd[1]);
} else {
// Parent process
close(pipefd[1]); // Close the write end
read(pipefd[0], buf, sizeof(buf)); // Read data from pipe
printf("Received: %s\n", buf);
close(pipefd[0]);
}
return 0;
}
bash
Copy code
mkfifo /tmp/myfifo
c
Copy code
#include <stdio.h>
#include <fcntl.h>
#include <unistd.h>
int main() {
int fd = open("/tmp/myfifo", O_WRONLY);
write(fd, "Hello from writer", 18);
close(fd);
return 0;
}
c
Copy code
#include <stdio.h>
#include <fcntl.h>
#include <unistd.h>
int main() {
char buf[100];
int fd = open("/tmp/myfifo", O_RDONLY);
read(fd, buf, sizeof(buf));
printf("Received: %s\n", buf);
close(fd);
return 0;
}
Conclusion:
Unnamed pipes are simpler, temporary, and suitable for related processes.
Named pipes (FIFOs) are more flexible, persistent, and allow communication between unrelated
processes, though they require careful management of file system resources.
Pipes are fundamental in process communication, allowing efficient and direct data transfer
between processes.
ii) Which operation are performed by the kernel during execution of fork( )?
Answer:-
When the fork() system call is invoked in a Unix-like operating system, the kernel performs
several critical operations to create a new process (the child process) by duplicating the calling
process (the parent process). Here’s a step-by-step breakdown of the operations performed by the
kernel during the execution of fork():
1. Process Identification:
o The kernel generates a unique Process ID (PID) for the new process (child
process). The child process gets a new PID, while the parent process retains its
original PID.
2. Copying Process Address Space:
o The address space of the parent process is copied to the child process. This
includes:
Code segment (the program's instructions).
Data segment (variables, heap).
Stack segment (execution context, local variables).
o This copying is typically done using a copy-on-write (COW) mechanism, which
means the memory pages are not copied immediately but are marked as read-only.
If either the parent or child process modifies any of these pages, the kernel copies
the page at that point (lazy copy). This optimization helps in avoiding
unnecessary copying of memory.
3. Copying File Descriptors:
o The kernel creates a copy of the file descriptors for the child process. Both the
parent and the child process share the same open files (i.e., the file descriptor
tables are identical).
o However, the file descriptor tables are not duplicated immediately but rather point
to the same underlying file objects. This means that file operations performed by
either process will affect the same open file.
4. Parent and Child Process Execution Context:
o The execution context (including the program counter, registers, etc.) is
duplicated. The parent and child processes continue execution at the instruction
immediately after the fork() call.
o The return value of fork() is different for the parent and child:
In the parent process, fork() returns the PID of the child process.
In the child process, fork() returns 0.
5. Resource Usage:
o The child process inherits certain resources from the parent, such as:
Signal handlers: The child process inherits a copy of the signal handlers
of the parent process.
Process groups: The child process belongs to the same process group as
the parent.
6. Parent-Child Relationship:
o The kernel establishes a parent-child relationship between the two processes.
The child process is assigned a parent PID (PPID), which is the PID of the
parent process.
o The parent process can later wait for the child to terminate using wait() or
waitpid(), and it can retrieve the exit status of the child.
7. Scheduling:
o The child process is initially placed in the ready queue by the kernel, ready to be
scheduled by the scheduler. It is typically executed after the parent process,
depending on the operating system’s scheduling policies.
8. Memory Management (Copy-on-Write):
o If copy-on-write (COW) is used (which is common in modern systems for
efficiency), the child process and parent process initially share the same memory
pages. The kernel ensures that when either process tries to modify a shared page,
a copy of the page is created (hence the term "copy-on-write").
9. Setting up the PCB (Process Control Block):
o The kernel creates a Process Control Block (PCB) for the child process. The
PCB contains important information about the process, such as:
The process state.
The program counter.
The CPU register values.
The memory management information.
The file descriptor table.
The signal handlers.
10. Return to User Space:
o After all the necessary operations are performed, the kernel returns control to the
user-space of both the parent and child processes. The parent receives the child's
PID as the return value, and the child receives 0.
int main() {
// Set up the signal handlers
if (signal(SIGINT, handler1) == SIG_ERR) {
perror("Error setting handler1");
exit(1);
}
return 0;
}
Explanation of the Code:
1. Global Flag:
o volatile int flag is used to store the result of the signal handler. The keyword
volatile tells the compiler not to optimize this variable because it will be
modified asynchronously by the signal handler.
2. Signal Handlers:
o handler1() and handler2() are the two signal handlers for SIGINT. These
handlers modify the global flag variable, setting it to 1 and 2 respectively.
3. Race Condition Simulation:
o The signal_race_condition() function raises the SIGINT signal periodically
with a sleep delay to simulate multiple signals being sent in an asynchronous
manner.
o The race condition occurs because the handlers for SIGINT are being invoked by
multiple threads, and the timing of their execution is not guaranteed. This can lead
to an unpredictable outcome.
4. Thread Creation:
o The program creates a thread that continuously sends SIGINT signals, which will
invoke the signal handlers. As the signal handlers are asynchronous, it simulates a
race condition between the handlers.
5. Signal Registration:
o The signal handlers are registered using signal(). However, a problem in this
program is that the second call to signal() overwrites the first signal handler.
This is a demonstration of a race condition: the program sets two handlers for the
same signal (SIGINT), and due to the lack of atomicity in signal handling, the
handlers overwrite each other, causing unpredictable results.
6. Thread Join:
o The pthread_join() call makes the main thread wait for the created thread
(though the thread will keep running indefinitely as it raises signals in a loop).
Expected Output:
Since signal handlers are asynchronous, you might observe unpredictable output depending on
how the kernel handles the signals and which handler is executed first. Example output might be:
vbnet
Copy code
Handler 2: Signal caught, flag set to 2
Handler 2: Signal caught, flag set to 2
Handler 1: Signal caught, flag set to 1
Handler 2: Signal caught, flag set to 2
...
Here, handler2 might overwrite handler1 depending on the timing of signal delivery, causing a
race condition where both handlers try to handle the same signal, and the final value of flag
could be inconsistent or unpredictable.
1. Signal Overwriting: The race condition is primarily caused by the fact that both
handler1 and handler2 are set for the same signal (SIGINT). The second call to
signal(SIGINT, handler2) overwrites the first handler, so the behavior becomes
unpredictable.
2. Concurrency in Signal Delivery: The signal handler execution is concurrent with the
main process. Both handler1 and handler2 might attempt to modify the flag variable
at the same time, which could lead to inconsistent values for flag.
Q5) Attempt the following:
a) i) Under which circumstances the process is swapped out?
Answer:-
In Unix-like operating systems, a process can be swapped out (i.e., moved from main memory
to swap space on disk) under certain circumstances to manage system resources effectively.
Swapping is part of virtual memory management, where the operating system moves
processes between RAM and disk to ensure that the system remains responsive even when there
are more processes than can fit into physical memory.
1. Memory Overcommit:
When the system is under memory pressure (i.e., when there is not enough physical
memory to accommodate all running processes), the operating system may decide to
swap out some processes to free up memory. This typically occurs when:
o The system's RAM is full or nearly full.
o A process tries to allocate more memory than is available in the physical memory.
If the system runs low on available physical memory (RAM), the operating system may
swap out processes that are not actively running (i.e., processes that are idle or in the
background) to disk, to make room for processes that require more memory.
Swapping allows the system to continue running by moving less critical processes to
disk.
Processes that are not actively using the CPU or are in a waiting or sleeping state (such
as waiting for I/O operations to complete) are prime candidates for swapping out. Since
they are not using resources heavily, they can be safely swapped out to disk.
Swapping out idle processes ensures that the operating system can allocate more memory
to processes that are currently active and require CPU resources.
When there are more processes running than the physical memory can handle, the
operating system must decide which processes to swap out. Processes that have a low
priority or low activity (e.g., background tasks) are more likely to be swapped out.
This ensures that more critical processes (such as those with higher priority or those
actively using the CPU) are given enough memory.
5. Explicit Memory Management by the Kernel:
The kernel may decide to swap out processes as part of its memory management
strategy. The kernel can swap processes in and out of physical memory based on the
current system workload and available resources.
For example, the Linux kernel uses a mechanism called the out-of-memory (OOM)
killer to swap out processes or terminate them if the system is critically low on memory.
The process might be swapped out when part of its virtual memory (such as a page of
memory) is not currently needed or is not being accessed frequently. This is part of
demand paging, where the operating system moves inactive pages to the swap space on
disk. If the process needs the swapped-out pages again, they are swapped back into
memory.
This typically happens when there is paging activity, meaning parts of a process's
memory are moved to swap space to free up RAM.
The operating system's scheduler may choose to swap out processes in order to optimize
system performance. If there are many processes in the system, the kernel may decide to
swap out less active ones to make room for more important or higher-priority processes
that need memory and CPU time.
Answer:-
In Unix-like operating systems, a regular file is a file that contains user data, as opposed to
special files like directories, device files, or symbolic links. A regular file stores data in a linear
sequence of bytes, and this structure allows it to represent various types of information, including
text files, binary files, images, executables, and more.
The structure of a regular file can be broken down into different components, primarily involving
the file metadata and the data content. Below is a breakdown of the structure:
The inode does not store the file name or the actual data content of the file; it only
contains metadata. The file name is stored in the directory entry that maps to the inode.
2. Data Blocks:
o The data blocks of a regular file store the actual content of the file. The file data
is divided into fixed-size blocks, and the operating system uses these blocks to
store the file's content.
The size of these blocks depends on the filesystem, commonly 4KB or
8KB per block.
The inode contains pointers to the data blocks, and if the file is large, the
inode can use multiple levels of pointers (direct, indirect, double indirect,
and triple indirect) to address all the data blocks.
3. File Name:
o The file name is stored in the directory entry that corresponds to the file. This
entry maps the file name to its inode, which in turn contains the file metadata and
pointers to its data blocks.
o A directory is essentially a special type of file that contains entries mapping file
names to inode numbers.
mathematica
Copy code
+-------------------------------------------------+
| Directory Entry |
|-------------------------------------------------|
| File Name -> "example.txt" |
| Inode Number -> 12345 |
+-------------------------------------------------+
+-------------------------------------------------+
| Inode |
|-------------------------------------------------|
| File Type: Regular File |
| File Permissions: rw-r--r-- |
| Owner: User1 |
| Group: Group1 |
| Size: 1024 bytes |
| Last Modified: 2024-12-04 12:34:56 |
| Last Accessed: 2024-12-04 12:34:56 |
| Block Pointers: |
| Direct Block 1 -> 2050 |
| Direct Block 2 -> 2051 |
| Indirect Block -> 3001 |
+-------------------------------------------------+
+-------------------------------------------------+
| Data Blocks |
|-------------------------------------------------|
| Block 2050: Data content (1024 bytes) |
| Block 2051: Data content (1024 bytes) |
| Block 3001: Indirect Block (Pointer to data) |
+-------------------------------------------------+
1. Directory Entry:
o The directory entry stores the file name and maps it to an inode number. In this
case, the file name "example.txt" is mapped to inode number 12345.
2. Inode:
o The inode is a data structure that stores metadata for the file. It contains
information such as file permissions, size, timestamps, and pointers to the data
blocks that hold the file content. The inode for "example.txt" contains:
Information like file type (regular file), file permissions (rw-r--r--), owner,
and timestamps.
Pointers to data blocks:
Direct Block Pointers (Block 2050 and 2051): These blocks
contain parts of the actual file content.
Indirect Block Pointer (Block 3001): If the file is large, the inode
may contain indirect block pointers. These are used when the
number of direct pointers exceeds the number of available slots in
the inode.
3. Data Blocks:
o Data blocks store the actual content of the file. For example, Block 2050 and
Block 2051 might contain data (like text or binary data) stored in 1024-byte
chunks. The indirect block (Block 3001) may point to additional data blocks if the
file grows too large for the available direct pointers.
Key Points:
Inode: Stores metadata, file permissions, timestamps, and pointers to the data blocks.
Data Blocks: Store the actual file content.
Directory Entry: Maps the file name to its inode.
For larger files, the inode can have multiple levels of indirection to address data blocks:
This structure allows for efficient file storage and access, enabling files of varying sizes to be
managed effectively by the operating system.
b) C program that creates a child process to read commands from the standard input and execute
them.You can assume that no arguments will be passed to the commands to be executed.
Answer:-
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
if (pid < 0) {
// Error occurred while creating the child process
perror("Fork failed");
exit(1);
}
else if (pid == 0) {
// In the child process
printf("Exiting program\n");
return 0;
}
Explanation:
1. Reading Commands:
o The program continuously prompts the user for input using fgets(), which reads
a line from the standard input (keyboard). It stores this input in the command array.
The fgets() function also includes the newline character (\n) when the user
presses Enter, so we use strcspn() to remove this newline before processing the
command.
2. Child Process Creation (fork):
o fork() is used to create a new process. It creates a child process that is a copy of
the parent process. The pid variable holds the process ID returned by fork().
o If fork() fails, an error message is printed, and the program exits with a non-zero
status.
3. Child Process Execution (execvp):
o In the child process (pid == 0), the command is executed using execvp(). The
execvp() function replaces the current process image with a new process image
specified by the command.
o The execvp() function requires two arguments:
The command to execute (command).
An array of arguments, which is typically an array where the first element
is the command itself and the following elements are the arguments for
that command. In this case, no arguments are passed to the command, so
the array contains just the command name.
4. Parent Process (wait):
o In the parent process (pid > 0), the program waits for the child process to finish
using the wait() function. The wait() function causes the parent process to wait
until any child process terminates. The status of the child process is stored in the
status variable.
o After the child process terminates, the program prompts the user for the next
command.
5. Exit Condition:
o The program terminates when the user enters the command exit. If the command
is "exit", the break statement will exit the while loop, and the program will print
"Exiting program" and terminate.
Example Output:
bash
Copy code
Enter command: ls
file1.txt file2.txt directory1