NAME:KURUVA THIRUMALESH
MIS:112415111
GROUP:3
OSLAB
Demonstration of Linux/Unix Process-Related System
Calls
Objective:
The main objective is to understand and demonstrate Linux process management by
implementing and analyzing process-related system calls such as fork(), wait(),
exec(), exit(), getpid(), getuid(), setuid(), brk(), nice(), and sleep(), in
order to study process creation, execution, synchronization, memory management,
scheduling, and termination in a Unix-based operating system.
Description:
● fork() is a system call in Unix-like operating systems that creates a child
process from an existing Parent Process.
● getpid() is a system call in Linux used to get the process Id of the calling
process. It is commonly used with fork() to identify whether code is running
in the parent or child process.
Code:
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
int main() {
int i;
pid_t pid;
pid_t parent_pid = getpid();
for(i = 0; i < 5; i++) {
pid = fork();
if(pid == 0) {
printf("hello, i am a child(pid:%d)\n", getpid());
return 0;
} else if(pid > 0) {
printf("hello, i am parent of %d(pid:%d)\n", parent_pid, getpid());
} else {
printf("Fork failed\n");
}
}
return 0;
}
Output:
Remarks:
The lab effectively demonstrates Linux/Unix process-related system calls covering all
key aspects of process management. fork() and wait() correctly illustrate process
creation and synchronization, while exec() and exit() demonstrate execution and
termination. getpid() and getuid() provide process identification, setuid()
covers privilege management, brk() handles memory management, and nice() with
sleep() demonstrate scheduling and process delays. The output observations align
with expected Unix behavior, and the implementation reflects a clear understanding of
OS process management concepts.
OSLAB_ASSIGNMENT
Below is a simple lab report format for all 5 problems based on your assignment. The
assignment tasks are listed in your uploaded document.
1. System Call: square()
1. Objective
To create a system call square(int n) in xv6 that returns the square of a number.
2. Description
This system call takes an integer as input and returns its square. The computation is
done in the kernel and returned to the user program.
3. Code
Kernel function (sysproc.c)
int
sys_square(void)
int n;
argint(0, &n);
return n*n;
User program (square.c)
#include "types.h"
#include "stat.h"
#include "user.h"
int main()
int n = 5;
printf(1,"Square of %d is %d\n", n, square(n));
exit();
4. Output
$ square
Square of 5 is 25
5. Remarks
The square of the given integer is successfully computed using a system call.
2. System Call: getnproc()
1. Objective
To implement a system call getnproc() that returns the number of active processes
in the system.
2. Description
This system call counts processes whose state is not UNUSED in the process table.
3. Code
Kernel function
int
sys_getnproc(void)
struct proc *p;
int count = 0;
for(p = [Link]; p < &[Link][NPROC]; p++){
if(p->state != UNUSED)
count++;
return count;
User program (nproc.c)
#include "types.h"
#include "stat.h"
#include "user.h"
int main()
printf(1,"Active processes: %d\n", getnproc());
exit();
}
4. Output
$ nproc
Active processes: 3
5. Remarks
The system call successfully counts the total active processes in the system.
3. System Call: forkcount()
1. Objective
To implement a system call forkcount() that returns how many times fork() has
been called.
2. Description
A global counter is maintained in proc.c. Every time fork() is executed, the counter
is incremented.
3. Code
Kernel function
int
sys_forkcount(void)
return fork_counter;
}
User program (forkcount.c)
#include "types.h"
#include "stat.h"
#include "user.h"
int main()
printf(1,"Fork called %d times\n", forkcount());
exit();
4. Output
$ forkcount
Fork called 1 times
5. Remarks
The system call correctly counts how many times the fork operation is executed.
4. System Call: getmemsize()
1. Objective
To create a system call getmemsize() that returns memory used by the current
process.
2. Description
The memory size of the current process is stored in the process structure field sz.
This value is returned.
3. Code
Kernel function
int
sys_getmemsize(void)
return myproc()->sz;
User program (memsize.c)
#include "types.h"
#include "stat.h"
#include "user.h"
int main()
printf(1,"Memory size: %d bytes\n", getmemsize());
exit();
4. Output
$ memsize
Memory size: 4096 bytes
5. Remarks
The system call successfully returns the memory size used by the process.
5. System Call: filecount()
1. Objective
To implement a system call filecount() that returns the number of open files in the
system.
2. Description
The system counts files in the global file table whose reference count is greater than
zero.
3. Code
Kernel function
int
sys_filecount(void)
struct file *f;
int count = 0;
for(f = [Link]; f < &[Link][NFILE]; f++){
if(f->ref > 0)
count++;
return count;
}
User program (filecount.c)
#include "types.h"
#include "stat.h"
#include "user.h"
int main()
printf(1,"Open files: %d\n", filecount());
exit();
4. Output
$ filecount
Open files: 3
5. Remarks
The system call correctly counts the number of currently open files in the system.
SCREENSHOT
Implementing different CPU scheduling in xv6-public
proc.c:
nano schedtest.c
OUTPUT:
Bankers’ algorithm code file:
SYSPROC.C CODE
CODE:
int sys_banker(void)
{
int n = 5, m = 3;
int alloc[5][3] = {
{0,1,0},
{2,0,0},
{3,0,2},
{2,1,1},
{0,0,2}
};
int max[5][3] = {
{7,5,3},
{3,2,2},
{9,0,2},
{2,2,2},
{4,3,3}
};
int avail[3] = {3,3,2};
int need[5][3];
int finish[5] = {0};
int safe[5];
int count = 0;
// Calculate Need
for(int i=0;i<n;i++)
for(int j=0;j<m;j++)
need[i][j] = max[i][j] - alloc[i][j];
while(count < n){
int found = 0;
for(int i=0;i<n;i++){
if(finish[i] == 0){
int j;
for(j=0;j<m;j++){
if(need[i][j] > avail[j])
break;
}
if(j == m){
for(int k=0;k<m;k++)
avail[k] += alloc[i][k];
safe[count++] = i;
finish[i] = 1;
found = 1;
}
}
}
if(!found){
cprintf("System is NOT safe\n");
return 0;
}
}
cprintf("System is SAFE\nSequence: ");
for(int i=0;i<n;i++)
cprintf("P%d ", safe[i]);
cprintf("\n");
return 0;
}
code in nano banktest.c
OUTPUT:
OS Lab assignment – Problems on
Synchronisation
1)
CODE:
#include <iostream>
#include <string>
using namespace std;
int mutex_sem = 1;
int patientReady = 0;
int doctorReady = 0;
void wait_sem(int &s)
{
s--;
}
void signal_sem(int &s)
{
s++;
}
struct Buffer
{
int patientID;
string symptoms;
string treatment;
} sharedBuffer;
void consultDoctor(int id)
{
[Link] = id;
[Link] = "Fever";
cout << "Patient " << id << " wrote symptoms to buffer." << endl;
}
void treatPatient()
{
cout << "Doctor reading symptoms of Patient "
<< [Link] << ": "
<< [Link] << endl;
[Link] = "Take Medicine";
cout << "Doctor wrote treatment for Patient "
<< [Link] << "." << endl;
}
void noteTreatment(int id)
{
cout << "Patient " << id
<< " reads treatment: "
<< [Link] << endl;
cout << "-------------------------" << endl;
}
void patient(int id)
{
cout << "Patient " << id << " entering waiting room..." << endl;
wait_sem(mutex_sem);
consultDoctor(id);
signal_sem(patientReady);
}
void doctor()
{
wait_sem(patientReady);
treatPatient();
signal_sem(doctorReady);
}
void patientLeaves(int id)
{
wait_sem(doctorReady);
noteTreatment(id);
signal_sem(mutex_sem);
}
int main()
{
int numPatients = 3;
for (int i = 1; i <= numPatients; i++)
{
cout << "\n=== Visit " << i << " ===" << endl;
patient(i);
doctor();
patientLeaves(i);
}
return 0;
}
OUTPUT:
3)
Q.3 Consider the readers and writers problem discussed in class. Recall that multiple
readers can be allowed to read concurrently, while only one writer at a time can access the
critical section. Write down pseudocode to implement the functions readLock, readUnlock,
writeLock, and writeUnlock that are invoked by the readers and writers to realize read/write
locks. You must use only semaphores, and no other synchronization mechanism, in your
solution. Further, you must avoid using more semaphores than is necessary. Clearly list all
the variables (semaphores, and any other flags/counters you may need) and their initial
values at the start of your solution. Use the notation down(x) and up(x) to invoke atomic
down and up operations on a semaphore x that are available via the OS API. Use sensible
names for your variables
Ans.
CODE:
#include <iostream>
using namespace std;
struct Shared {
int data;
int flag = 0;
} sh;
int produceNext() {
static int x = 1;
return x++;
}
void consumeNext(int x) {
cout << "Consumed: " << x << endl;
}
int main() {
while (true) {
if ([Link] == 0) {
int val = produceNext();
[Link] = val;
[Link] = 1;
}
if ([Link] == 1) {
int val = [Link];
[Link] = 0;
consumeNext(val);
}
}
return 0;
}
OUTPUT:
4)
Q.4 Consider the readers and writers problem as discussed in class. Several reader
and writer processes wish to access a critical section. Because readers do not modify
the critical section, multiple readers can access the critical section concurrently.
However, a writer can access the critical section only when no other reader or writer
is concurrently accessing it. We wish to implement locking/synchronization between
readers and writers, while giving preference to writers, where no waiting writer should
be kept waiting for longer than necessary. For example, suppose reader process R1 is
actively reading. And a writer process W1 and reader process R2 arrive while R1 is
reading. While it might be fine to allow R2 in, this could prolong the waiting time of
W1 beyond the absolute minimum of waiting until R1 finishes. Therefore, if we want
writer preference, R2 should not be allowed before W1. Your goal is to write down
pseudocode for read lock, read unlock, write lock, and write unlock functions that the
processes should call, in order to realize read/write locks with writer preference. You
must use only simple locks/mutexes and conditional variables in your solution.
Please pick sensible names for your variables so that your solution is readable.
CODE:
#include <stdio.h>
int readcount = 0;
int writecount = 0;
void reader() {
if (writecount > 0) {
printf("Reader waiting (writer active)\n");
return;
}
readcount++;
printf("Reader reading\n");
readcount--;
if (readcount == 0) {
printf("Readers finished, writer can proceed\n");
}
}
void writer() {
writecount++;
if (readcount > 0) {
printf("Writer waiting (readers active)\n");
writecount--;
return;
}
printf("Writer writing\n");
writecount--;
printf("Writer finished, readers can proceed\n");
}
int main() {
reader();
writer();
reader();
writer();
return 0;
}
OUTPUT:
5)
void transfer(struct account *from, struct account *to, int amount) {
struct account *first;
struct account *second;
if (from->accountnum < to->accountnum) {
first = from;
second = to;
} else {
first = to;
second = from;
}
dolock(&first->lock);
dolock(&second->lock);
from->balance -= amount;
to->balance += amount;
unlock(&second->lock);
unlock(&first->lock);
}
CODE IN VSCODE:
#include <stdio.h>
struct account {
int accountnum;
int balance;
};
void transfer(struct account *from, struct account *to, int amount) {
from->balance -= amount;
to->balance += amount;
}
int main() {
struct account a = {1, 1000};
struct account b = {2, 1000};
transfer(&a, &b, 200);
printf("A: %d, B: %d\n", [Link], [Link]);
return 0;
}
OUTPUT:
Q6
Modern operating systems use pushcli() and popcli() instead of directly using
cli() and sti() to safely handle nested critical sections.
If interrupts were enabled immediately after a single sti() call, while another critical
section was still active, an interrupt handler could run and attempt to acquire a lock
that is already held. This may lead to deadlocks, race conditions, or corruption of
kernel data.
pushcli() maintains a count of how many times interrupts have been disabled. Each
call to pushcli() increments the count, and each popcli() decrements it. Interrupts
are re-enabled using sti() only when the count becomes zero. Therefore, interrupts
remain disabled until the outermost critical section completes, ensuring correct
synchronization and safe execution inside the kernel.
Q7
PID-based wakeup can cause starvation to processes with high PID values and does
not guarantee bounded waiting. Replacing it with a FIFO method of waiting processes
ensures fairness and bounded wait time.
Q8
In the exit() function itself.
Q9
Although sleep() releases the caller’s lock lk before yielding the CPU, it first
acquires [Link] and holds it while marking the process as sleeping and
invoking the scheduler. Since wakeup() also requires [Link], it cannot run
concurrently during this critical transition. This ensures that no wakeup is missed and
preserves the atomicity of the sleep operation.
Q10
If yield() sets the process state to runnable before acquiring [Link], the
scheduler may see the process as runnable and schedule it on another CPU while it is
still executing. This can result in the same process running on multiple CPUs
simultaneously, causing race conditions and corruption of process state.
Acquiring [Link] before changing the state ensures that the transition to
runnable and yielding the CPU happen atomically.
Q11
A newly created process in xv6 starts execution in forkret instead of directly in
trapret because some important kernel initialization work must be completed before
returning to user mode.
The most important task is releasing [Link], which is still held by the
scheduler when the new process is first scheduled. If the process directly entered
trapret, the lock would remain held, preventing other processes and scheduler
operations from working correctly and possibly causing deadlock.
Thus, the process first runs forkret, which performs necessary setup work
(including releasing [Link]) and then returns to trapret, which restores the
trap frame and switches the process safely to user mode.
Q12
Separate Ready Queues
Better when there are multiple jobs of different types. One queue can schedule long
tasks without pre-emption, while another queue can schedule shorter tasks with
pre-emption.
Single Ready Queue
Better for load sharing across all CPU cores.
Q13
Answer: B
Q14
Answer: B
Q15
Answer: A
Q16
Answer: D