0% found this document useful (0 votes)
8 views39 pages

AOS paper solution

Uploaded by

cssalunke79
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as DOCX, PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
8 views39 pages

AOS paper solution

Uploaded by

cssalunke79
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as DOCX, PDF, TXT or read online on Scribd
You are on page 1/ 39

Q1) Solve any 5 of the following: [5×2=10]

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:

1. Positive pid (e.g., pid > 0):


o The signal is sent to the process with the specified process ID (pid).
o Example: kill(1234, SIGTERM) sends the SIGTERM signal to the process with
PID 1234.
2. pid equal to 0:
o The signal is sent to all processes in the same process group as the calling process
(except the calling process itself in some implementations).
o This is often used to send a signal to a group of related processes.
3. Negative pid (e.g., pid < -1):
o The signal is sent to all processes in the process group whose process group ID is
equal to the absolute value of pid.
o Example: kill(-123, SIGKILL) sends the SIGKILL signal to all processes in the
process group with ID 123.
4. pid equal to -1:
o The signal is sent to all processes that the calling process has permission to
signal, excluding special system processes like init (PID 1) or itself.
o This is often used for system-wide signaling but should be used cautiously to
avoid unintentional disruptions.

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.

c) What is the difference between ‘wait’ and ‘waitpid’?

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.

2. Control Over Child Processes

 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).

3. Support for Non-blocking Behavior

 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.

4. Arguments and Options

 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

Feature wait waitpid


Wait for specific PID No Yes
Non-blocking support No Yes (WNOHANG)
Control over children Minimal Flexible
Use case Simple process management Complex scenarios

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:

 fd: File descriptor of the open file.


 offset: The offset to apply.
 whence: Specifies how the offset is applied. It can be one of:
o SEEK_SET: Set the file offset to offset.
o SEEK_CUR: Set the file offset to the current offset plus offset.
o SEEK_END: Set the file offset to the end of the file plus offset.

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.

e) What is broken link?

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. A symbolic link /home/user/link points to /home/user/target.


2. If /home/user/target is deleted or moved, the link /home/user/link becomes broken.

Causes of Broken Links:

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.

Detection of Broken Links:

Broken symbolic links can be identified using commands like:

 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).

Impact in Advanced Operating Systems:

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.

f) How to obtain process ID and parent Process ID?


Answer:-

1. What are PID and PPID?

 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.

2. How to Obtain PID and PPID:

A. In C using System Calls (POSIX):

In Unix-like operating systems (e.g., Linux), you can use system calls to retrieve the PID and
PPID.

 getpid(): Retrieves the PID of the current process.


 getppid(): Retrieves the PPID of the current process.

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

printf("Process ID (PID): %d\n", pid);


printf("Parent Process ID (PPID): %d\n", ppid);

return 0;
}
Explanation:

 getpid() returns the unique identifier of the current process.


 getppid() returns the PID of the process that created the current process (i.e., the parent).
g) What is the output of following code? #include #include int main( ) { if (fork( ) & & (!fork()))
{ if (fork( ) || fork( )) { fork( ); } } printf(“2”); return 0; }

Answer:-

The output will be the number 2 printed 8 times.

Q2) Attempt the following: [12]


a) i) What is a process? Draw and Explain state transition diagram of a process
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.

State Transition Diagram of a 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) |
+------------------------+

Explanation of Process States:

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.

4. Waiting (or Blocked):


o A process is in the Waiting or Blocked state when it is waiting for an event to occur,
such as I/O completion or a resource becoming available. While in this state, the process
cannot continue execution and needs to be moved back to the ready state once the
event happens.

5. Terminated (or Exit):


o A process enters the Terminated state when it finishes executing or is terminated by the
operating system or another process. In this state, the resources allocated to the
process are released, and the process control block (PCB) is deleted.
State Transitions:

 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.

ii) Explain any three data structure for Demand Paging.


Answer:-

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:

Assuming a page table for a process with 4 pages:

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:

 Pages 0 and 1 are in memory at frames 2 and 4, respectively.


 Pages 2 and 3 are not in memory and are on disk.

2. Swap Space / Swap File (Swap Table)

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:

 Page Number: The virtual page number of the swapped-out page.


 Swap Block Number: The location in the swap space where the page is stored.
 Reference Count: The number of references to the page in memory, used to track the necessity
of swapping it back into RAM.

Example of a Swap Table:


markdown
Copy code
Swap Table:
Page Number | Swap Block | Reference Count
-----------------------------------------
2 | 1024 | 3
3 | 2048 | 1

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.

3. Translation Lookaside Buffer (TLB)

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.

Components of the TLB:

 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:

For a system with a 4-entry TLB:

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.

b) Explain syntax of following system call. [5]


i) alarm( )
ii) kill( )
iii) sbrk( )
iv) execl( )
v) fchmod( )

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:

 Returns 0 if the signal was successfully sent.


 Returns -1 if there was an error (e.g., if the process does not exist or permission is denied).

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:

 path: The path to the executable file (e.g., /bin/ls).


 arg: The first argument to the new program (usually the name of the program).
 The remaining arguments are passed to the new program, ending with a NULL pointer.

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.

Q3) Attempt the following:

a) i) Explain fourth scenario for buffer allocation.


Answer:-
https://github.com/suvratapte/Maurice-Bach-Notes/blob/
master/3-The-Buffer-Cache.md

refer this link for above answer


ii) Explain the behaviour of the following C program: [3]
#include main(int argc, char *argv[ ]) { int fd, skval; char c; if(argc! = 2) exit( );
while(skval = read(fd, & c, 1)) { printf(“char%c\n”, c); skval=Iseek(fd, 1023L, 1);
printf(“new seek val%d\n”, skval); } }

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:

1. Uninitialized File Descriptor (fd) in read() call:


o In the original code, the file descriptor (fd) was not initialized before it was used
in the read() function. The corrected code properly opens the file first before
reading from it.
2. Error Handling for lseek():
o The lseek() function call should be properly handled. If the lseek() fails, it
returns -1, and the program should handle this error (e.g., print an error message
and exit).
3. Loop Exit Condition:
o The while (skval = read(fd, &c, 1)) loop relies on the return value of
read(). When read() returns 0 (EOF), the loop will exit. This is the expected
behavior.

Sample Output:

Assume the input file contains the following data: Hello World!

1. First Iteration (Read 'H'):

arduino
Copy code
char H
new seek val 1024

2. Second Iteration (Read 'e'):

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>

void print_file_type(const char *filename) {


struct stat statbuf;

// Get the file status using stat()


if (stat(filename, &statbuf) == -1) {
perror("Error");
return;
}

// Check file type and print corresponding message


if (S_ISREG(statbuf.st_mode)) {
printf("'%s' is a regular file.\n", filename);
}
else if (S_ISDIR(statbuf.st_mode)) {
printf("'%s' is a directory.\n", filename);
}
else if (S_ISLNK(statbuf.st_mode)) {
printf("'%s' is a symbolic link.\n", filename);
}
else if (S_ISCHR(statbuf.st_mode)) {
printf("'%s' is a character device.\n", filename);
}
else if (S_ISBLK(statbuf.st_mode)) {
printf("'%s' is a block device.\n", filename);
}
else if (S_ISFIFO(statbuf.st_mode)) {
printf("'%s' is a FIFO (named pipe).\n", filename);
}
else if (S_ISSOCK(statbuf.st_mode)) {
printf("'%s' is a socket.\n", filename);
}
else {
printf("'%s' is of an unknown file type.\n", filename);
}
}

int main(int argc, char *argv[]) {


// Ensure at least one command line argument is provided
if (argc < 2) {
printf("Usage: %s <file1> <file2> ... <fileN>\n", argv[0]);
exit(1);
}

// 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:

1. stat() System Call:


o The stat() system call is used to obtain information about the file, and it stores
the result in the struct stat.
o The statbuf.st_mode field contains the file's type and permissions, which can
be checked using macros.
2. Checking File Type:
o The S_IS*() macros are used to determine the type of file:
 S_ISREG(): Regular file
 S_ISDIR(): Directory
 S_ISLNK(): Symbolic link
 S_ISCHR(): Character device
 S_ISBLK(): Block device
 S_ISFIFO(): FIFO (named pipe)
 S_ISSOCK(): Socket
o If the file type does not match any of these, it is considered an "unknown file
type."
3. Command-Line Arguments:
o The program takes command-line arguments and uses a loop to check the type of
each file.
o If no arguments are passed, it prints the usage message and exits.
4. Error Handling:
o If stat() fails (e.g., the file does not exist), the program prints an error message
using perror() and returns.

Sample Output:

Let's say we have the following files in the current directory:

 file1.txt (a regular file)


 directory (a directory)
 symlink (a symbolic link)
Running the program like this:

bash
Copy code
./filetype file1.txt directory symlink

The output might be:

csharp
Copy code
'file1.txt' is a regular file.
'directory' is a directory.
'symlink' is a symbolic link.

Q4) Attempt the following: [12]


a) i) What are pipes? Explain named pipes and unnamed pipes?
Answer:-

Pipes in Operating Systems

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:

There are two main types of pipes:

1. Unnamed Pipes (Anonymous Pipes)


2. Named Pipes (FIFOs)

1. Unnamed Pipes (Anonymous Pipes):

 Definition: An unnamed pipe is a simple, temporary communication channel used


between processes that have a parent-child relationship or between related processes. It is
called "unnamed" because it doesn't have a specific name in the file system.
 Creation: Unnamed pipes are created using the pipe() system call. It creates a pipe with
two file descriptors: one for reading and one for writing.
o Example:

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).

2. Named Pipes (FIFOs):

 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.

Comparison between Unnamed and Named Pipes:

Feature Unnamed Pipes (Anonymous) Named Pipes (FIFOs)

Creation Created using pipe() call Created using mkfifo() call or mknod()

Temporary; no name or reference in the


Visibility Persistent; has a name in the filesystem
filesystem

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:

 Unnamed Pipe Example (Parent-Child Communication):

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;
}

 Named Pipe Example:


1. Create a named pipe:

bash
Copy code
mkfifo /tmp/myfifo

2. Writer Process (C program):

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;
}

3. Reader Process (C program):

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():

Operations Performed by the Kernel During fork() Execution:

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.

b) Write a C program to demonstrate race condition in catching signals


Answer:-
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <signal.h>
#include <pthread.h>

volatile int flag = 0; // A shared variable to show race condition

// Signal handler for SIGINT


void handler1(int sig) {
flag = 1; // Set flag when first handler is called
printf("Handler 1: Signal caught, flag set to 1\n");
}

// Another signal handler for SIGINT


void handler2(int sig) {
flag = 2; // Set flag when second handler is called
printf("Handler 2: Signal caught, flag set to 2\n");
}

// Function to simulate a signal race condition


void* signal_race_condition(void* arg) {
while (1) {
// Simulate a race condition by sending SIGINT signal
raise(SIGINT);
sleep(1); // Delay to simulate time gap between signals
}
}

int main() {
// Set up the signal handlers
if (signal(SIGINT, handler1) == SIG_ERR) {
perror("Error setting handler1");
exit(1);
}

if (signal(SIGINT, handler2) == SIG_ERR) {


perror("Error setting handler2");
exit(1);
}

// Create a thread to simulate sending signals


pthread_t thread;
if (pthread_create(&thread, NULL, signal_race_condition,
NULL) != 0) {
perror("Error creating thread");
exit(1);
}

// Join the thread (though it will run indefinitely in this case)


pthread_join(thread, NULL);

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.

What Happens in This Race Condition?

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.

Here are the circumstances under which a process is swapped out:

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.

2. Low System 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.

3. Process Not Actively Using CPU (Idle Processes):

 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.

4. System Load (High Number of Processes):

 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.

6. Virtual Memory Paging:

 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.

7. Scheduling and Process Management:

 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.

8. System Configuration (Swappiness):

 The behavior of swapping is influenced by system configuration parameters, such as the


swappiness value in Linux. Swappiness determines how likely the system is to swap out
processes when memory is under pressure.
o A high swappiness value (e.g., 60) indicates that the system will prefer to swap
processes out more aggressively.
o A low swappiness value (e.g., 10) means the system will avoid swapping out
processes unless absolutely necessary.
 The kernel uses this parameter to balance the need for memory with the need to keep
processes in physical RAM.

9. Resource Allocation Strategies:

 Some resource allocation strategies, such as load balancing or memory compression,


might lead the kernel to decide which processes to swap out in order to allocate more
memory to processes with higher demands. This helps in ensuring that the system
continues to function efficiently under high load.

When a Process is Swapped In:


 When a process that has been swapped out needs to be executed again (e.g., it becomes
active after being idle or receives input/output), the operating system will swap it back
in from the disk to RAM, which can cause a delay, as disk access is slower than memory
access.

ii) Explain the structure of regular file with suitable diagram?

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.

Structure of a Regular File:

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:

1. File Metadata (Inode):


o Inode (Index Node) is a data structure that stores the metadata of the regular file.
It contains information about the file, such as:
 File type (e.g., regular file, directory, symbolic link).
 File permissions (read, write, execute).
 Owner and group of the file.
 File size (in bytes).
 Timestamps (creation time, last access time, last modification time).
 Link count (number of hard links to the file).
 Pointers to data blocks (addresses where the file content is stored on
disk).

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.

Diagram of the Structure of a Regular File:

Here's a conceptual diagram to show the structure:

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) |
+-------------------------------------------------+

Explanation of the Diagram:

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.

Multiple Levels of Indirection:

For larger files, the inode can have multiple levels of indirection to address data blocks:

 Direct Pointers: Point directly to data blocks.


 Indirect Pointer: Points to a block that contains additional pointers to data blocks.
 Double Indirect Pointer: Points to a block that contains pointers to other blocks, which
in turn contain pointers to data blocks.
 Triple Indirect Pointer: An additional level of indirection for extremely large files.

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>

#define MAX_COMMAND_LENGTH 1024


int main() {
char command[MAX_COMMAND_LENGTH];
pid_t pid;
int status;

// Loop to continuously read and execute commands


while (1) {
// Print a prompt to the user
printf("Enter command: ");

// Read the command from the standard input


if (fgets(command, MAX_COMMAND_LENGTH, stdin) == NULL) {
perror("Error reading input");
exit(1);
}

// Remove newline character if present


command[strcspn(command, "\n")] = 0;

// If the user enters "exit", terminate the program


if (strcmp(command, "exit") == 0) {
break;
}

// Create a child process using fork()


pid = fork();

if (pid < 0) {
// Error occurred while creating the child process
perror("Fork failed");
exit(1);
}
else if (pid == 0) {
// In the child process

// Execute the command using execvp


// execvp requires an array of strings, so we need to split the command
char *args[] = {command, NULL}; // No arguments, only the command itself
if (execvp(command, args) == -1) {
// If execvp fails
perror("Execvp failed");
exit(1);
}
}
else {
// In the parent process
// Wait for the child process to complete
wait(&status);
}
}

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

Enter command: date


Tue Dec 4 12:34:56 UTC 2024

Enter command: exit


Exiting program

You might also like