Lab Manual: Department of Computer Science and Engineering
Lab Manual: Department of Computer Science and Engineering
Prepared by
Syllabus
PART-A
1. Write a C/C++ POSIX compliant program to check the following limits:
(i) No. of clock ticks
(ii) Max. no. of child processes
(iii) Max. path length
(iv) Max. no. of characters in a file name
(v) Max. no. of open files/ process
2. Write a C/C++ POSIX compliant program that prints the POSIX defined configuration
options supported on any given system using feature test macros.
3. Consider the last 100 bytes as a region. Write a C/C++ program to check whether the
region is locked or not. If the region is locked, print pid of the process which has locked. If
the region is not locked, lock the region with an exclusive lock, read the last 50 bytes and
unlock the region.
4 Write a C/C++ program which demonstrates interprocess communication between a reader
process and a writer process. Use mkfifo, open, read, write and close APIs in your program.
5 a) Write a C/C++ program that outputs the contents of its Environment list
b) Write a C / C++ program to emulate the unix ln command
6 Write a C/C++ program to illustrate the race condition.
7 Write a C/C++ program that creates a zombie and then calls system to execute the ps
command to verify that the process is zombie.
8 Write a C/C++ program to avoid zombie process by forking twice.
9 Write a C/C++ program to implement the system function.
10 Write a C/C++ program to set up a real-time clock interval timer using the alarm API.
PART-B
11. Write a C program to implement the syntax-directed definition of “if E then S1” and “if
E then S1 else S2”. (Refer Fig. 8.23 in the text book prescribed for 06CS62 Compiler
Design, Alfred V Aho, Ravi Sethi, and Jeffrey D Ullman: Compilers- Principles, Techniques
and Tools, 2nd Edition, Pearson Education, 2007).
12. Write a yacc program that accepts a regular expression as input and produce its parse
tree as output.
Note: In the examination each student picks one question from the lot of all 12 questions
Content Sheet
Explanation:
Limits:
#include <unistd.h>
The difference between the last two functions is that one takes a pathname as its
argument and the other takes a file descriptor argument.
Table 1 lists the name arguments that sysconf uses to identify system limits.
Constants beginning with _SC_ are used as arguments to sysconf to identify the runtime
limit. Table 2 lists the name arguments that are used by pathconf and fpathconf to
identify system limits. Constants beginning with _PC_ are used as arguments to pathconf
and fpathconf to identify the runtime limit
Algorithm
Step 1: Start
Step 2: Query the clock tick limits
Step 3: If yes, print the number of clock ticks. Go to Step 5
Step 4: If no, print the feature is not supported
Step 5: Query the limit on max. no. of child process supported
Step 6: If yes, print the max. no. of child process supported Go to
Step 8
Step 7: If no, print the feature is not supported
Step 8: Query the limit on max. no. of characters in the pathname
Step 9: If yes, print the max. no. of characters in the pathname.
Go to Step 11
Step 10: If no, print the feature is not supported
Step 11: Query the limit on max. no. of characters in the filename
Step 12: If yes, print the max. no. of characters in the filename.
Go to Step 14
Step 13: If no, print the feature is not supported
Step 14: Query the limit on max. no. of files that can be opened
Step 15: If yes, print the max. no. of files that can be opened. Go
to Step 17
Step 16: If no, print the feature is not supported
Step 17: End
Program
#define _POSIX_SOURCE
#define _POSIX_C_SOURCE 199309L
#include<unistd.h>
#include<iostream.h>
int main()
{
int res;
if((res=sysconf(_SC_CLK_TCK))==-1)
cout << "clock ticks not supported" << endl;
else
cout << "No of clock ticks is " <<res <<endl;
if((res=sysconf(_SC_CHILD_MAX))==-1)
cout << "child max not supported" << endl;
else
cout << "No of child processes that can be created
simulataneously " <<res <<"Min value" << _POSIX_CHILD_MAX<<endl;
if((res=pathconf("/",_PC_PATH_MAX))==-1)
cout << "path max not supported" << endl;
else
cout << "Max path length is " <<res <<endl;
if((res=pathconf("/",_PC_NAME_MAX))==-1)
cout << "name max not supported" << endl;
else
cout << "Max size of file name " <<res <<endl;
if((res=sysconf(_SC_OPEN_MAX))==-1)
cout << "open max not supported" << endl;
else
cout << "No of files/processes that can be opened simultaneously
is " <<res <<endl;
Output
2. Write a C/C++ POSIX compliant program that prints the POSIX defined configuration options
supported on any given system using feature test macros
Explanation:
POSIX.1 defines a set of feature test macro‟s which if defined on a system, means
that the system has implemented the corresponding features. All these test macros are
defined in <unistd.h> header.
Algorithm:
Step 1: Start
Step2: Check whether Job control is supported.
Step 3: If yes, print the message “job control is supported”. Go to
Step 5
Step 4: Print the message “Job control is not supported”.
Step 5: Check whether Saved IDs is supported.
Step 6: If yes, print the message “Saved IDs is supported”. Go to
Step 7
Step 7: Print the message “Saved IDs is not supported”.
Step 8: Check whether Change of Ownership is supported.
Step 9: If yes, print the message “Change of ownership is
supported” and also print its value. Go to Step 11
Step 10: Print the message “Change of ownership is not supported”.
Step 11: Check whether No Truncation option is supported.
Step 12: If yes, print the message “No Truncation is supported” and
also print its value. Go to Step 14
Step 13: Print the message “No Truncation option is not supported”.
Step 14: Check whether Vdisabling is supported.
Step 15: If yes, print the message “VDisabling is supported” and
also print its value. Go to Step 11
Step 16: Print the message “VDisabling is not supported”.
Step 17: End
#ifdef _POSIX_JOB_CONTROL
cout << "Job control is supported" <<endl;
#else
cout << "Job control is not supported" << endl;
#endif
#ifdef _POSIX_SAVED_IDS
cout << "Saved Ids is supported" <<endl;
#else
cout << "Saved Ids is not supported" << endl;
#endif
#ifdef _POSIX_CHOWN_RESTRICTED
cout << "Change of ownership is supported and value is "<<
_POSIX_CHOWN_RESTRICTED<<endl;
#else
cout << "Change of ownership is not supported"<<endl;
#endif
#ifdef _POSIX_NO_TRUNC
cout << "No truncation is supported and value is "<<
_POSIX_NO_TRUNC<<endl;
#else
cout << "No truncation is not supported"<<endl;
#endif
#ifdef _POSIX_VDISABLE
cout << "Disabling is supported and disabling char is"<<
_POSIX_VDISABLE<<endl;
#else
cout << "Disabling is not supported"<<endl;
#endif
Output
sleep(5);
cout << "\033[1;34m";
#ifdef _POSIX_NO_TRUNC
cout << "No truncation is supported and value is "<<
_POSIX_NO_TRUNC<<endl;
#else
cout << "No truncation is not supported"<<endl;
#endif
sleep(5);
cout << "\033[1;35m";
#ifdef _POSIX_VDISABLE
cout << "Disabling is supported and disabling char is"<<
_POSIX_VDISABLE<<endl;
#else
cout << "Disabling is not supported"<<endl;
#endif
cout << "\033[0m";
}
Output
3. Consider the last 100 bytes as a region. Write a C/C++ program to check whether the region is
locked or not. If the region is locked, print pid of the process which has locked. If the region is
not locked, lock the region with an exclusive lock, read the last 50 bytes and unlock the region.
Explanation
Multiple processes performs read and write operation on the same file
concurrently.
This provides a means for data sharing among processes, but it also renders
difficulty for any process in determining when the other process can override data
in a file.
So, in order to overcome this drawback UNIX and POSIX standard support file
locking mechanism.
File locking is applicable for regular files.
UNIX systems provide fcntl function to support file locking. By using fcntl it is possible to
impose read or write locks on either a region or an entire file
The prototype of fcntl is:
#include<fcntl.h>
int fcntl(int fdesc, int cmd_flag, ....);
The first argument specifies the file descriptor for a file to be processed.
The second argument cmd_flag specifies what operation has to be performed.
The possible cmd_flag values are defined in the <fcntl.h> header. The specific
values for file locking and their uses are:
cmd_flag Use
F_SETLK sets a file lock, do not block if this cannot succeed immediately.
F_SETLKW sets a file lock and blocks the process until the lock is acquired.
F_GETLK queries as to which process locked a specified region of file.
For file locking purpose, the third argument to fctnl is an address of a struct flock
type variable.
This variable specifies a region of a file where lock is to be set, unset or queried.
The struct flock is declared in the <fcntl.h> as:
struct flock
{
short l_type; /* what lock to be set or to unlock file */
short l_whence; /* Reference address for the next field */
off_t l_start ; /*offset from the l_whence reference addr*/
off_t l_len ; /*how many bytes in the locked region */
pid_t l_pid ; /*pid of a process which has locked the file */
};
The l_whence, l_start & l_len define a region of a file to be locked or unlocked.
The l_whence field defines a reference address to which the l_start byte offset
value is added. The possible values of l_whence and their uses are:
Algorithm
Step 1: Start
Step 2: Open a file for reading and writing
Step 3: Try to obtain an exclusive lock for the last 100 bytes of
the file
Step 4: Check, if some process has put a read lock
Step 5: If yes, print the message “process.. has put a read lock”,
wait until process unlocks
Step 6: If no, check if some process has put a write lock
Step 7: If yes, print the message “process .. has put a write
lock”, wait until process unlocks
Step 8: Put an exclusive lock with waiting option for last 100
bytes
Step 9: Simulate some 10 sec delay for processing
Step 10: Read last 50 bytes of data from the file into a memory
buffer
Step 11: Display the buffer
Step 12: Unlock the file
Step 13: End
Program
#include<fcntl.h>
#include<iostream.h>
#include<stdlib.h>
int main()
{
char buf[50];
int fd=open("b.txt",O_RDWR);
struct flock f1;
f1.l_type=F_WRLCK;
f1.l_whence=SEEK_END;
f1.l_start=0;
f1.l_len=100;
fcntl(fd,F_GETLK,&f1);
if(f1.l_type==F_RDLCK)
{
cout << f1.l_pid << " has put read lock" << endl;
// exit(0);
}
if(f1.l_type==F_WRLCK)
{
cout << f1.l_pid << " has put write lock" << endl;
//exit(0);
}
f1.l_type=F_WRLCK;
f1.l_pid=getpid();
fcntl(fd,F_SETLKW,&f1);
sleep(10);
lseek(fd,-50,SEEK_END);
read(fd,buf,50);
cout << buf;
f1.l_type=F_UNLCK;
fcntl(fd,F_SETLK,&f1);
Output
Explanation:
It is a special pipe device file which provides a temporary buffer for two or more
processes to communicate by writing data to and reading data from the buffer. The size
of the buffer is fixed to PIPE_BUF. Data in the buffer is accessed in a first-in-first-out
manner. The buffer is allocated when the first process opens the FIFO file for read or
write. The buffer is discarded when all processes close their references (stream pointers)
to the FIFO file. Data stored in a FIFO buffer is temporary.
#include<sys/types.h>
#include<sys/stat.h>
#include<unistd.h>
int mkfifo(const char *path_name, mode_t mode);
open:
This is used to establish a connection between a process and a file i.e. it is used to
open an existing file for data transfer function or else it may be also be used to create a
new file. The returned value of the open system call is the file descriptor (row number of
the file table), which contains the inode information.
The prototype of open function is,
#include<sys/types.h>
#include<sys/fcntl.h>
int open(const char *pathname, int accessmode, mode_t permission);
read:
The read function fetches a fixed size of block data from a file referenced by a given file
descriptot
The prototype of read function is:
#include<sys/types.h>
#include<unistd.h>
size_t read(int fdesc, void *buf, size_t nbyte);
write:
The write system call is used to write data into a file. The write function puts data to a file in
the form of fixed block size referred by a given file descriptor.
Algorithm:
Step 1: Start
Step 2: Create a FIFO file
Step 3: Check whether task is for reader or writer
Step 4: If reader , else go to Step 5
Step 4.1: Open FIFO file in read mode
Step 4.2: Load FIFO file contents into buffer
Step 4.3: Display the buffer. Go to Step 6
Step 5: If writer
Step 5.1: Open FIFO file in write mode
Step 5.2: Write user data into FIFO file
Step 6: End
Program
#include<sys/types.h>
#include<sys/stat.h>
#include<fcntl.h>
#include<iostream.h>
int main(int argc,char *argv[])
{
char buf[100];
mkfifo(argv[1],S_IFIFO|0777);
if(argc==3)
{
int fd=open(argv[1],O_WRONLY);
write(fd,argv[2],strlen(argv[2]));
close(fd);
}
if(argc==2)
{
int fd=open(argv[1],O_RDONLY);
int n=read(fd,buf,sizeof(buf));
buf[n]='\0';
cout << buf << endl;
close(fd);
}
Output : (Case -1: Writer background, Reader foreground
Case-2: Reader background, Writer foreground)
5. a) Write a C/C++ program that outputs the contents of its Environment list.
Explanation:
Each program is also passed an environment list. Like the argument list, the
environment list is an array of character pointers, with each pointer containing the
address of a null-terminated C string. The address of the array of pointers is contained in
the global variable environ:
extern char **environ;
Algorithm: Main
Step 1: Start
Step 2: Create an environment list (name=value format)
Step 3: Execute a program that can display all environment
variables
Step 4: End
Program:Main
#include<unistd.h>
#include<sys/types.h>
#include<sys/wait.h>
int main()
{
pid_t pid;
pid=fork();
char *e[]={"name=chetan","key=jnnce",NULL};
if(pid==0)
{
execve("/root/usp/s51",NULL,e);
}
else
{
wait(NULL);
}
}
i=0;
while(environ[i]!=NULL)
{
cout << environ[i] << ",";
i++;
}
}
Output:
Explanation:
Hard and Symbolic Links
link:
The link function creates a new hard link for the existing file.
#include <unistd.h>
int link(const char *cur_link, const char *new_link);
Symlink:
A symbolic link is created with the symlink function.
The prototype of the symlink function is
#include <unistd.h>
int symlink(const char *actualpath , const char *sympath);
Algorithm
Step 1: Start
Step 2: Check if user has asked for symbolic link file
Step 3: If yes,
Step 3.1 validate source and destination files
Step 3.2 : Create symbolic link file. Go to Step 5
Step 4: If no,
Step 4.1 validate source and destination files
Step 4.2 : Create hard link file.
Step 5: End
Program:
#include<unistd.h>
#include<iostream.h>
#include<string.h>
int main(int argc,char *argv[])
{
if(argc<3 || argc>4)
{
cout << "Error in usage"<<endl;
return(-1);
}
if(argc==4 && strcmp(argv[1],"-s")!=0)
{
cout << "for symbolic link use -s option" << endl;
return(-1);
}
Output:
Explanation:
A race condition occurs when multiple processes are trying to do something with
shared data and the final outcome depends on the order in which the processes run. It is
also defined as; an execution ordering of concurrent flows that results in undesired
behavior is called a race condition-a software defect and frequent source of
vulnerabilities.
Race condition is possible in runtime environments, including operating systems
that must control access to shared resources, especially through process scheduling.
Algorithm:
Step 1: Start
Step 2: Initialize two messages, one each for parent and child
process
Step 3: Create a process
Step 4: If child, print child message character by character.
Go to Step 6
Step 5: If parent, print parent message character by character.
Step 6: End
Program:
#include<unistd.h>
#include<sys/types.h>
#include<iostream.h>
void cat(char*);
int main()
{
char *p="jnnce shimoga vtu future of excellence qqqqqqqq";
char *c="computer science and engineering- bringing up excellence
rrrrrrrr";
pid_t pid;
pid=fork();
if(pid==0)
{
cat(c);
exit(0);
}
else
{
cat(p);
}
}
void cat(char *str)
{
cout.flush();
for(int i=0;i<strlen(str);i++)
{
cout.flush();
Output:
7. Write a C/C++ program that creates a zombie and then calls system to execute the
ps command to verify that the process is zombie.
Explanation:
A zombie process or defunct process is a process that has completed execution
but still has an entry in the process table. This entry is still needed to allow the parent
process to read its child's exit status. The term zombie process derives from the common
definition of zombie - an undead person.
Algorithm:
Step 1: Start
Step 2: Create a process
Step 3: If process is child, exit and go to Step 6
Step 4: If process is parent, display process status
Step 5: Illustrate zombie state
Step 6: End
Program:
#include<unistd.h>
#include<sys/types.h>
#include<iostream.h>
int main()
{
pid_t pid;
pid=fork();
if(pid==0)
{
cout << "child" <<endl;
exit(0);
}
else
{
char str[100],b[10];
//system("ps -o stat,pid,ppid,command");
strcpy(str,"ps -o stat,pid,ppid,command --pid ");
sprintf(b,"%d",pid);
strcat(str,b);
system(str);
sleep(10);
cout << "parent";
}
}
Output:
Explanation
When a process terminates, either normally or abnormally, the kernel notifies the
parent by sending the SIGCHLD signal to the parent. Because the termination of a child
is an asynchronous event - it can happen at any time while the parent is running - this
signal is the asynchronous notification from the kernel to the parent. The parent can
choose to ignore this signal, or it can provide a function that is called when the signal
occurs: a signal handler. A process that calls wait or waitpid can:
Algorithm:
Step 1: Start
Step 2: Create a process
Step 3: If child, create another process Go to Step 5
Step 4: If parent, Wait for the death of the child. Go to Step 8
Step 5: If parent, exit and go to Step 8
Step 6: If child, Sleep for 20 secs
Step 7: print the process status (zombie not created)
Step 8: End
Program:
#include<unistd.h>
#include<sys/types.h>
#include<iostream.h>
#include<sys/wait.h>
int main()
{
pid_t cid,gcid;
cid=fork();
if(cid==0)
{
cout << "child id " <<getpid()<<endl;
gcid=fork();
if(gcid==0)
{
cout << "grandchild " <<getpid()<<endl;
sleep(20);
system("ps -o stat,pid,ppid,command");
}
else
{
exit(0);
}
}
else
{
Output:
Explanation:
When a process calls one of the exec functions, that process is completely
replaced by the new program, and the new program starts executing at its main function.
The process ID does not change across an exec, because a new process is not created;
exec merely replaces the current process - its text, data, heap, and stack segments - with a
brand new program from disk.
#include <unistd.h>
int execl(const char *pathname, const char *arg0,... /* (char *)0 */ );
int execv(const char *pathname, char *const argv [ ]);
int execle(const char *pathname, const char *arg0,... /*(char *)0, char *const envp */ );
int execve(const char *pathname, char *const argv[ ], char *const envp[]);
int execlp(const char *filename, const char *arg0, ... /* (char *)0 */ );
int execvp(const char *filename, char *const argv [ ]);
System() function:
It is convenient to execute a command string from within a program. ANSI C
defines the system function, but its operation is strongly system dependent. The system
function is not defined by POSIX.1 because it is not an interface to the operating system,
but really an interface to a shell.
#include <stdlib.h>
int system(const char *cmdstring);
value from system is the termination status of the shell, in the format specified for
waitpid.
Algorithm:
Step 1: Start
Step 2: Register to ignore Interrupt and Terminate signals
Step 3: Create a process
Step 4: If parent, wait for the death of the child Go to Step 7
Step 5: If child, execute a shell program with command line options
Step 6: return exit status
Step 7: Print the exit status
Step 8: End
Program:
#include<unistd.h>
#include<sys/types.h>
#include<sys/wait.h>
#include<iostream.h>
#include<signal.h>
pid=fork();
if(pid<0)
{
status=-1;
}
if(pid==0)
{
execl("/bin/sh","sh","-c",cmd,(char*)0);
_exit(127);
}
else
{
waitpid(pid,&status,0);
}
return(status);
}
10. Write a C/C++ program to set up a real-time clock interval timer using the
alarm API.
Explanation:
Alarm() function:
The alarm API can be called by a process to request the kernel to send the
SIGALRM signal after a certain number of real clock seconds. The function prototype of
the API is:
#include<signal.h>
unsigned int alarm(unsigned int time_interval);
#include <unistd.h>
int pause(void);
The pause() library function causes the invoking process (or thread) to sleep until
a signal is received that either terminates it or causes it to call a signal-catching function.
The pause() function only returns when a signal was caught and the signal-catching
function returned. In this case pause() returns -1, and errno is set to EINTR.
Sigaction() function:
The sigaction API blocks the signal it is catching allowing a process to specify
additional signals to be blocked when the API is handling a signal. The sigaction API
prototype is:
#include<signal.h>
int sigaction(int signal_num, struct sigaction* action, struct sigaction* old_action);
Returns: 0 if OK, 1 on error The struct sigaction data type is defined in the <signal.h>
header as:
struct sigaction
{
void (*sa_handler)(int);
sigset_t sa_mask;
int sa_flag;
}
Algorithm:
Step 1: Start
Step 2: Accept starting time and interval value in seconds from user
Step 3: Register for alarm signal
Step 4: Set up alarm with starting time value
Step 5: When alarm expires, reset alarm with a diagnostic message
Step 6: End
Program:
#include<signal.h>
#include<iostream.h>
int m,n;
void g1(int signo)
{
cout << "wake up" <<endl;
alarm(n);
}
int main(int argc,char *argv[])
{
sscanf(argv[1],"%d",&m);
sscanf(argv[2],"%d",&n);
struct sigaction s;
sigemptyset(&s.sa_mask);
s.sa_flags=0;
s.sa_handler=g1;
sigaction(SIGALRM,&s,0);
alarm(m);
while(1);
}
Output:
Explanation:
Syntax Directed Definition:
A Syntax directed definition is a generalization of a context free grammar in
which each grammar symbol has an associated set of attributes, partitioned into two
subsets called the synthesized and inherited attributes of that grammar symbol.
An attribute can represent anything we choose: a string, a number, a type, a
memory location, or whatever. The value of an attribute at a parse tree node is defined by
a semantic rule associated with a production used at that node. The value of a synthesized
attribute at a node is computed from the values of attributes at the children of that node in
the parse tree; the value of an inherited attribute is computed from the values of attributes
at the siblings and parent of that node.
Semantic rules set up dependencies between attributes that will be represented by
a graph. From the dependency graph, we derive an evaluation order for the semantic
rules. Evaluation of the semantic rules defines the values of the attributes at the nodes in
parse tree for the input string.
A parse tree showing the values of attributes at each node is called an annotated
parse tree. The process of computing the attributes at the nodes is called annotating or
decorating the parse tree.
SDD for simple if and if-else
Simple if
S → if E then S1
E.true := new_label()
E.false :=S.next
S1.next :=S.next
S.code :=E.code | | gen_code(E.true „: „) | | S1.code
If-else
S → if E then S1 else S2
E.true := new_label()
E.false := new_label()
S1.next :=S.next
S2.next :=S.next
S.code :=E.code | | gen_code(E.true „: „) |S1.code | gen_code(“goto”,S.nex)
|| gen_code(E.false,”:”) || S2.code
scanf("%[^\n]s",str);
if(strstr(str,"if")!=NULL && strstr(str,"else")==NULL)
{
sscanf(str,"if %s then %s",expr,tstmt);
printf("100: if %s then goto 102\n",expr);
printf("101:goto 103\n");
printf("102:%s\n",tstmt);
printf("103:....\n");
}
else if(strstr(str,"if")!=NULL && strstr(str,"else")!=NULL)
{
sscanf(str,"if %s then %s else %s",expr,tstmt,fstmt);
printf("100: if %s then goto 102\n",expr);
printf("101:%s, goto 103\n",fstmt);
printf("102:%s\n",tstmt);
printf("103:....\n");
}
}
Output:
int main()
{
char str[100],expr[100],tstmt[100],fstmt[100];
printf("enter the if statement\n");
scanf("%[^\n]s",str);
if(strstr(str,"if")!=NULL && strstr(str,"else")==NULL)
{
int p1=position_if(str);
int p2=position_then(str);
str_bet(str,p1,p2-5,expr);
str_bet(str,p2,strlen(str)-1,tstmt);
printf("100: if %s then goto 102\n",expr);
printf("101:goto 103\n");
printf("102:%s\n",tstmt);
printf("103:....\n");
}
else if(strstr(str,"if")!=NULL && strstr(str,"else")!=NULL)
{
int p1=position_if(str);
int p2=position_then(str);
int p3=position_else(str);
str_bet(str,p1,p2-5,expr);
str_bet(str,p2,p3-5,tstmt);
str_bet(str,p3,strlen(str)-1,fstmt);
printf("100: if %s then goto 102\n",expr);
printf("101:%s, goto 103\n",fstmt);
printf("102:%s\n",tstmt);
printf("103:....\n");
}
}
int position_if(char *str)
{
int i=0;
for(i=0;i<strlen(str);i++)
{
if(str[i]=='i' && str[i+1]=='f')
return(i+2);
return(-1);
}
return(-1);
}
return(-1);
}
Output:
12. Write a yacc program that accepts a regular expression as input and produce
its parse tree as output.
Explanation:
Regular Expression:
A regular expression is a specific pattern that provides concise and flexible means
to "match" (specify and recognize) strings of text, such as particular characters, words, or
patterns of characters. A regular expression provides a grammar for a formal language;
this specification can be interpreted by a regular expression processor, which is a
program that either serves as a parser generator or examines text and identifies substrings
that are members of the specified (again, formal) language.
Derivations:
To check whether a sequence of tokens is legal or not, we start with a nonterminal
called the start symbol. We apply productions, rewriting nonterminals, until only
terminals remain. A derivation replaces a nonterminal on LHS of a production with RHS.
Parse Tree:
Is a graphical representation for a derivation. It filters out choice regarding
replacement order. It is rooted by the start symbol S, interior nodes represent
nonterminals in N, leaf nodes are terminals in T or node A can have children X1,
X2,...Xn if a rule A->X1, X2... Xn exists.
Algorithm:
Step 1: Start
Step 2: Scan a regular expression character by character
Step 3: For each character, return token ALPHABET if it is alphabet,
else return value
Step 4: Write a grammar that recursively defines regular
expressions:
re-> re.re |re|re | re+ | re* | ALPHABET
Step 5: For each rule, store corresponding pattern in a production
array
Step 6: Start traversing production from end
Step 7: Print last element as it is
Step 8: For each remaining elements, replace rightmost regular
expression in previous value printed by current value in production
array
Step 9: End
Program : (RMD)
%{
#include<stdio.h>
#include<string.h>
char prod[10][200],temp[200],temp1[100];
int cnt=0,i,j;
%}
%token ALPHABET
%left '.'
%left '|'
%nonassoc '+' '*'
%%
S:re '\n'{
for(i=cnt-1;i>=0;i--)
{
if(i==cnt-1)
{
printf("%s\n",prod[i]);
strcpy(temp,prod[i]);
}
else
{
j=rmo(temp);
temp[j]='\0';
sprintf(temp1,"%s%s%s",temp,prod[i],temp+j+2);
printf("%s\n",temp1);
strcpy(temp,temp1);
}
}
exit(0);
}
re:re'|'re {strcpy(prod[cnt++],"re|re");}
|re'.'re {strcpy(prod[cnt++],"re.re");}
|re'*' {strcpy(prod[cnt++],"re*");}
|re'+' {strcpy(prod[cnt++],"re+");}
|'('re')' {strcpy(prod[cnt++],"(re)");}
| ALPHABET {prod[cnt][0]=yylval;prod[cnt++][1]='\0';}
%%
main()
{
yyparse();
}
int yylex()
{
int ch;
ch=getchar();
yylval=ch;
if(isalpha(ch))
return ALPHABET;
return ch;
}
int yyerror()
{
printf("invalid expr");
}
int rmo(char *str)
{
int i;
for(i=strlen(str)-1;i>=0;i--)
{
if(str[i]=='e' && str[i-1]=='r')
return(i-1);
}
}
Output:
for(i=cnt-1;i>=0;i--)
{
if(i==cnt-1)
{
printf("%s\n",prod[i]);
strcpy(temp,prod[i]);
}
else
{
j=lmo(temp);
temp[j]='\0';
sprintf(temp1,"%s%s%s",temp,prod[i],temp+j+2);
printf("%s\n",temp1);
strcpy(temp,temp1);
}
}
}
re:re'|'re {strcpy(prod[cnt++],"re|re");}
|re'.'re {strcpy(prod[cnt++],"re.re");}
|'*'re {strcpy(prod[cnt++],"re*");}
|'+'re {strcpy(prod[cnt++],"re+");}
|')'re'(' {strcpy(prod[cnt++],"(re)");}
| ALPHABET {prod[cnt][0]=yylval;prod[cnt++][1]='\0';}
%%
main()
{
scanf("%[^\n]s",buf);
for(i=strlen(buf)-1,j=0;i>=0;i--,j++)
bufr[j]=buf[i];
bufr[j]='\n';
yyparse();
}
int yylex()
{
int ch;
ch=bufr[cnti++];
//printf("a=%c",ch);
yylval=ch;
if(isalpha(ch))
return ALPHABET;
return ch;
}
int yyerror()
{
printf("Invalid expr");
}
int lmo(char *str)
{
int i;
for(i=0;i<=strlen(str)-1;i++)
{
if(str[i]=='r' && str[i+1]=='e')
return(i);
}
Output:
close(fd);
fd=open("a.txt",O_RDONLY);
int m=read(fd,buf,n);
close(fd);
cout << buf;
int fd=open("a.txt",O_WRONLY);
int n= write(fd,&b1,sizeof(b1));
close(fd);
fd=open("a.txt",O_RDONLY);
int m=read(fd,&b2,n);
close(fd);
cout << b2.name << b2.cost;
{
if(argc!=2)
cout << "error in usage";
else
unlink(argv[1]);
}
int main()
{
struct stat s1;
stat("a.c",&s1);
int rperm=S_IRWXG|S_IXOTH;
int aperm=s1.st_mode &~ rperm;
chmod("a.c",aperm);
#include<iostream.h>
int g=2;
int main()
{
int s=3;
pid_t pid;
pid=fork();
if(pid==0)
{
g++;
s++;
}
cout << "g="<<g <<",s="<<s<<endl;
}
//exit(0);
abort();
}
}
else
{
cout << "parent " <<getpid()<<endl;
int s;
wait(&s);
print_status(s);
}
}
void print_status(int status)
{
if(WIFEXITED(status))
cout << "normal termination" <<endl;
else if(WIFSIGNALED(status))
cout << "abnormal termination" <<endl;
else if(WIFSTOPPED(status))
cout << "stopped " <<endl;
}
}
}
15. Program to demonstrate the use of waitid API
#include<unistd.h>
#include<sys/types.h>
#include<iostream.h>
#include<sys/wait.h>
int main()
{
pid_t pid1,pid2,pid3;
pid1=fork();
if(pid1==0)
{
sleep(9);
exit(0);
}
pid2=fork();
if(pid2==0)
{
sleep(7);
exit(0);
}
pid3=fork();
if(pid3==0)
{
sleep(10);
exit(0);
}
else
{
cout << "parent id =" << getpid()<<endl;
cout <<
"child="<<pid1<<",child2="<<pid2<<",child3="<<pid3<<endl;
pid_t pid= waitid(P_PID,pid1,NULL,WEXITED);
cout << "the child died is " <<pid <<endl;
sscanf(argv[1],"%d",&m);
sscanf(argv[2],"%d",&n);
struct itimerval i;
i.it_interval.tv_sec=n;
i.it_interval.tv_usec=0;
i.it_value.tv_sec=m;
i.it_value.tv_usec=0;
struct sigaction s;
sigemptyset(&s.sa_mask);
s.sa_flags=0;
s.sa_handler=g1;
sigaction(SIGALRM,&s,0);
setitimer(ITIMER_REAL,&i,0);
while(1);
}
17. Program to avoid race condition by scheduling parent first, child next
#include<unistd.h>
#include<sys/types.h>
#include<iostream.h>
#include<sys/wait.h>
void cat(char*);
static void WAIT_PARENT();
static void TELL_CHILD();
static int s=1;
int main()
{
char *p="jnnce future of excellence";
char *c="computer science - bringing up excellence";
pid_t pid;
pid=fork();
if(pid==0)
{
WAIT_PARENT();
cat(c);
}
else
{
cat(p);
TELL_CHILD();
}
}
void cat(char *str)
{
for(int i=0;i<strlen(str);i++)
{
cout.flush();
cout << str[i];
}
}
void WAIT_PARENT()
{
while(getppid()!=1);
}
void TELL_CHILD()
{
exit(0);
}
18. Program to avoid race condition by scheduling child first and then parent
#include<unistd.h>
#include<sys/types.h>
#include<iostream.h>
#include<sys/wait.h>
void cat(char*);
int main()
{
pid_t pid;
pid=fork();
if(pid==0)
{
wait(NULL);
cat(c);
exit(0);
}
else
{
cat(p);
}
}
void cat(char *str)
{
for(int i=0;i<strlen(str);i++)
{
cout.flush();
cout << str[i];
}
}
p=getpwuid(s.st_uid);
cout << "\t" << p->pw_name;
g=getgrgid(s.st_gid);
cout << "\t" << g->gr_name;
}
void display_file_perm(struct stat s)
{
char dperm[]="rwxrwxrwx";
char aperm[10];
for(int i=0,j=(1<<8);i<9;i++,j=j>>1)
{
aperm[i]=(s.st_mode&j)?dperm[i]:'-';
}
if(s.st_mode&S_ISUID)
aperm[2]=(aperm[2]=='x')?'s':'S';
if(s.st_mode&S_ISGID)
aperm[5]=(aperm[5]=='x')?'s':'S';
if(s.st_mode&S_ISVTX)
aperm[8]=(aperm[8]=='x')?'t':'T';
aperm[9]='\0';
cout <<aperm;
}
void display_file_type(struct stat s)
{
switch(s.st_mode&S_IFMT)
{
case S_IFREG:cout << "-"; break;
case S_IFDIR:cout <<"d"; break;
case S_IFBLK:cout <<"b"; break;
case S_IFCHR:cout <<"c";break;
case S_IFIFO:cout << "p";break;
case S_IFLNK:cout<<"l";break;
}
}