UNIX SYSTEMS PROGRAMMING AND COMPILER DESIGN LABORATORY - 10CSL67
-
Upload
vivek-ranjan -
Category
Documents
-
view
621 -
download
1
description
Transcript of UNIX SYSTEMS PROGRAMMING AND COMPILER DESIGN LABORATORY - 10CSL67
UNIX SYSTEMS PROGRAMMING AND COMPILER DESIGN
LABORATORY - 10CSL67
POSIX an acronym for "Portable Operating System Interface" is a family of standards specified
by the IEEE for maintaining compatibility between operating systems. POSIX defines the
application programming interface (API), along with command line shells and utility interfaces,
for software compatibility with variants of Unix and other operating systems.
Define the macros by using ‘#define’ preprocessor directives at the top of your source code files.
These directives must come before any #include of a system header file.
_POSIX_SOURCE
If you define this macro, then the functionality from the POSIX.1 standard (IEEE
Standard 1003.1) is available, as well as all of the ISO C facilities.
The state of _POSIX_SOURCE is irrelevant if you define the macro
_POSIX_C_SOURCE to a positive integer.
_POSIX_C_SOURCE
Define this macro to a positive integer to control which POSIX functionality is made
available. The greater the value of this macro, the more functionality is made available.
If you define this macro to a value greater than or equal to 1, then the functionality from
the 1990 edition of the POSIX.1 standard (IEEE Standard 1003.1-1990) is made
available.
If you define this macro to a value greater than or equal to 2, then the functionality from
the 1992 edition of the POSIX.2 standard (IEEE Standard 1003.2-1992) is made
available.
If you define this macro to a value greater than or equal to 199309L, then the
functionality from the 1993 edition of the POSIX.1b standard (IEEE Standard 1003.1b-
1993) is made available.
A Example Program to check and display _POSIX_VERSION constant of the system on
which it runs.
#define _POSIX_SOURCE
#define _POSIX_C_SOURCE 200809L
#include <iostream.h>
#include <unistd.h>
int main()
{
#ifdef _POSIX_VERSION
cout << "System conforms to POSIX: " << _POSIX_VERSION << endl;
#else
cout << "_POSIX_VERSION is undefined\n";
#endif
return 0;
}
Work on the following list of POSIX.1 – defined constants available in the <limits.h> header.
POSIX.1 define constants are related to processes and files
Work on the following list of POSIX.1b – defined constants in the <limits.h> header.
POSIX.1b defined constants are related to real time operating system interfaces
including PCs
1. Write a C/C++ POSIX compliant program to check the following limits:
No. of clock ticks
Max. no. of child processes
Max. path length
Max. no. of characters in a file name
Max. no. of open files/ process
sysconf
This function is used to query general system wide configuration limits that are implemented on
a given system. The function prototype is:
long int sysconf (int parameter)
The parameter argument refers to any of the `_SC_' symbols listed in the table below. The
normal return value from sysconf is the value you requested. A value of -1 is returned both if the
implementation does not impose a limit, and in case of an error.
_SC_CLK_TCK
The number of clock ticks per second
_SC_CHILD_MAX
Maximum number of child processes that may be owned by a process simulataneously.
_SC_OPEN_MAX
Maximum number of opened files per process.
pathconf
When the machine allows different files to have different values for a file system parameter, use
these functions to find out the value that applies to any particular file. This function is used to
inquire about the limits that apply to the file named filename. These functions and the associated
constants for the parameter argument are declared in the header file `unistd.h'.
long int pathconf (const char *filename, int parameter)
The parameter argument should be one of the `_PC_' constants listed below. The normal return
value from pathconf is the value you requested. A value of -1 is returned both if the
implementation does not impose a limit, and in case of an error.
_PC_PATH_MAX
Maximum length, in bytes, of pathname.
_POSIX_NAME_MAX
Maximum length, in bytes, of filename.
#define _POSIX_SOURCE
#define _POSIX_C_SOURCE 200809L
#include<iostream>
using namespace std;
#include<unistd.h>
#include<limits.h>
int main()
{
cout<<"No. of clock ticks: " <<sysconf(_SC_CLK_TCK)<<endl;
cout<<"Max no. of child processes:" <<sysconf(_SC_CHILD_MAX)<<endl;
cout<<"Max path length:" <<pathconf("/",_PC_PATH_MAX)<<endl;
cout<<"Max characters in filen name:" <<pathconf("/",_PC_NAME_MAX)<<endl;
cout<<"Max no. of open files per process:" <<sysconf(_SC_OPEN_MAX)<<endl;
return 0;
}
/*OUTPUT*/
No. of clock ticks: 100
Max no. of child processes:1024
Max path length:4096
Max characters in filen name:255
Max no. of open files per process:1024
Work on the Following list of manifested constant supported by sysconf function are:
2. Write a C/C++ POSIX compliant program that prints the POSIX defined configuration
options supported on any given system using feature test macros.
The POSIX Feature Test Macros :Feature-test macros provide a means for writing portable
programs. It allows the programmer to control the definitions that are exposed by system header
files when a program is compiled. The exact set of features available when you compile a source
file is controlled by which feature test macros you define. 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. Some feature test macros are
useful for creating portable applications, by preventing nonstandard definitions from being
exposed. Other macros can be used to expose nonstandard definitions that are not exposed by
default. For the following macros used in the program , if the macro is defined in unistd.h, then the
option is supported.
_POSIX_JOB_CONTROL : If this symbol is defined, it indicates that the system supports job
control. Otherwise, the implementation behaves as if all processes within a session belong to a
single process group.
_POSIX_SAVED_IDS: If this symbol is defined, it indicates that the system remembers the
effective user and group IDs of a process before it executes an executable file with the set-user-
ID or set-group-ID bits set, and that explicitly changing the effective user or group IDs back to
these values is permitted. If this option is not defined, then if a nonprivileged process changes its
effective user or group ID to the real user or group ID of the process, it can't change it back again
_POSIX_CHOWN_RESTRICTED:If this option is in effect, the chown function is
restricted so that the only changes permitted to nonprivileged processes is to change the
group owner of a file to either be the effective group ID of the process, or one of its
supplementary group IDs.
_POSIX_NO_TRUNC:If this option is in effect, file name components longer than
NAME_MAX generate an ENAMETOOLONG error. Otherwise, file name components
that are too long are silently truncated.
_POSIX_VDISABLE:This option is only meaningful for files that are terminal devices.
If it is enabled, then handling for special control characters can be disabled individually.
#define _POSIX_SOURCE
#define _POSIX_C_SOURCE 200809L
#include <iostream>
#include <unistd.h>
using namespace std;
int main()
{
#ifdef _POSIX_JOB_CONTROL
cout << "System supports job control\n";
#else
cout << "System does not support job control\n";
#endif
#ifdef _POSIX_SAVED_IDS
cout << "System supports saved set-UID and saved set-GID\n";
#else
cout << "System does not support saved set-UID and saved set-GID\n";
#endif
#ifdef _POSIX_CHOWN_RESTRICTED
cout << "chown restricted option is: " << _POSIX_CHOWN_RESTRICTED <<
endl;
#else
cout << "System does not support system-wide chown_restricted option\n";
#endif
#ifdef _POSIX_NO_TRUNC
cout << "Pathname trucnation option is: " << _POSIX_NO_TRUNC << endl;
#else
cout << "System does not support system-wide pathname trucnation
option\n";
#endif
#ifdef _POSIX_VDISABLE
cout << "Diable character for terminal files is: " << _POSIX_VDISABLE <<
endl;
#else
cout << "System does not support _POSIX_VDISABLE\n";
#endif
return 0;
}
/*OUTPUT*/
System supports job control
System supports saved set-UID and saved set-GID
chown restricted option is: 0
Pathname trucnation option is: 1
Diable character for terminal files is:
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.
#include<iostream>
using namespace std;
#include<stdio.h>
#include<sys/types.h>
#include<fcntl.h>
#include<unistd.h>
int main(int argc, char* argv[])
{
struct flock fvar;
int fdesc;
while (--argc > 0)
{ /* do the following for each file */
if ((fdesc=open(*++argv,O_RDWR))==-1)
{
perror("open"); continue;
}
fvar.l_type = F_WRLCK;
fvar.l_whence = SEEK_SET;
fvar.l_start = 0;
fvar.l_len = 0;
/* Attempt to set an exclusive (write) lock on the entire file */
while (fcntl(fdesc, F_SETLK,&fvar)==-1)
{
/* Set lock fails, find out who has locked the file */
while(fcntl(fdesc,F_GETLK,&fvar)!=-1&& var.l_type!=F_UNLCK)
{
cout << *argv << " locked by " << fvar.l_pid<< " from " <<
fvar.l_start << " for "<< fvar.l_len << " byte for "
<<(fvar.l_type==F_WRLCK ? 'w' : 'r') << endl;
if (!fvar.l_len) break; fvar.l_start += fvar.l_len;
fvar.l_len = 0;
} /* while there are locks set by other processes */
}/* while set lock un-successful */
/* Lock the file OK. Now process data in the file */
/*... */
/* Now unlock the entire file */
fvar.l_type = F_UNLCK;
fvar.l_whence = SEEK_SET;
fvar.l_start = 0;
fvar.l_len = 0;
if (fcntl(fdesc, F_SETLKW,&fvar)==-1)
perror("fcntl");
}
return 0;
}
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.
Inter process communication using named pipes(FIFO)
A FIFO(a named pipe) is similar to a pipe, except that it is accessed as part of the file system. It
can be opened by multiple processes for reading or writing. When processes are exchanging data
via the FIFO, the kernel passes all data internally without writing it to the file system. A FIFO
(First In First Out) is a one-way flow of data. FIFOs have a name, so unrelated processes can
share the FIFO. The kernel maintains exactly one pipe object for each FIFO special file that is
opened by at least one process. The FIFO must be opened on both ends (reading and writing)
before data can be passed. Normally, opening the FIFO blocks until the other end is opened also.
A FIFO is created by the mkfifo function:
int mkfifo(const char *pathname, mode_t mode);
pathname – a UNIX pathname (path and filename).
mode – the file permission bits.
Open: mkfifo tries to create a new FIFO. If the FIFO already exists, then an EEXIST error is
returned.
Close: to close an open FIFO, use close().
Write:It writes data, in bytes as specified by the caller, from a buffer declared by the user in the
program and then writes it into the file supplied by the calling process.
size_t write(int fd, const void *buf, size_t nbytes);
Write, thus takes three arguments:
1. The file descriptor of the file (fd).
2. The buffer from where data is to be written into the file (buf).
3. The number of bytes to be read from the buffer (nbytes).
Read:This system call reads data, in bytes as specified by the caller, from the file and stores then
into a buffer supplied by the calling process.
ssize_t read(int fd, void *buf, size_t count);
The read system call can take three arguments:
1. The file descriptor of the file,
2. the buffer where the read data is to be stored and
3. the number of bytes to be read from the file.
#include <iostream>
using namespace std;
#include <errno.h>
#include <sys/types.h>
#include <unistd.h>
#include <fcntl.h>
#include <string.h>
#include <sys/stat.h>
int main( int argc, char* argv[])
{
if (argc !=2 && argc !=3)
{
cout << "usage: " << argv[0] << " <file> [<arg>]\n";
return 0;
}
int fd,count=0;
char buf[2560];
(void)mkfifo(argv[1],S_IFIFO|0777);/*reader process */
if(argc==2)
{
fd = open(argv[1],O_RDONLY|O_NONBLOCK);
//while (read(fd,buf,sizeof(buf))==-1 && errno==EAGAIN)
//sleep(1);
while (read(fd,buf,sizeof(buf)) > 0)
{
cout << buf << endl;
}
cout<<"Read completed";
}
else /* writer process */
{
fd = open(argv[1],O_WRONLY);
write(fd,argv[2],strlen(argv[2]));
cout<<"Write completed";
}
close(fd);
return 0;
}
5. a) Write a C/C++ program that outputs the contents of its Environment list
Environment list is an array of character pointers, with each pointer containing the address of a
null-terminated C string. Environment list as a set of parameters that are inherited from process
to process. Each program is also passed an environment list. Like the argument list. In almost all
operating systems each process has its own private set of environment variables. By default,
when a process is created it inherits a duplicate environment of its parent process, except for
explicit changes made by the parent when it creates the child. Running programs can access the
values of environment variables for configuration purposes.
Examples of environment variables include:
Path - lists directories the shell searches, for the commands the user may type without
having to provide the full path.
Temp - location where processes can store temporary files
UserProfile - indicate where a user's home directory is located in the file system.
Temp - location where processes can store temporary files.
TERM -The name of the user's terminal. Used to determine the capabilities of the
terminal.
USER- Current logged in user's name.
SHELL - The current shell.
The function prototype of the main function looks like:
int main(int argc, char *argv[]);
argc is non-negative - argument count - number of command-line arguments.
argv[argc] is a null pointer - argument vector - values of the program's command-line
arguments.
The names of argc and argv may be any valid identifier in C.
Unix (though not POSIX.1) and Microsoft Windows have a third argument giving the
program's environment,
int main(int argc, char **argv, char **envp);
By convention, the command-line arguments specified by argc and argv include the name of the
program as the first element if argc is greater than 0; if a user types a command of "rm file",
the shell will initialise the rm process with argc = 2 and argv = ["rm", "file", NULL]. envp is
available as an argument to main, as envp - a null terminated array of strings:
#include<unistd.h>
#include<iostream>
#include<stdlib.h>
using namespace std;
int main(int argc,char **argv,char **envp)
{
cout<<"ENVIRONMENT LIST:\n";
while(*envp!=NULL)
{
cout<<*envp<<endl;
envp++;
}
return 0;
}
/*OUTPUT*/
ENVIRONMENT LIST:
ORBIT_SOCKETDIR=/tmp/orbit-ksit
HOSTNAME=networklab1
IMSETTINGS_INTEGRATE_DESKTOP=yes
GPG_AGENT_INFO=/tmp/seahorse-O9TRh3/S.gpg-agent:2368:1;
TERM=xterm
SHELL=/bin/bash
XDG_SESSION_COOKIE=f0f769483ec1e461615dfc8c0000001c-1358851278.679103-
1904428157
HISTSIZE=1000
GTK_RC_FILES=/etc/gtk/gtkrc:/home/ksit/.gtkrc-1.2-gnome2
WINDOWID=39845891
QTDIR=/usr/lib/qt-3.3
QTINC=/usr/lib/qt-3.3/include
IMSETTINGS_MODULE=none
USER=ksit
b) Write a C / C++ program to emulate the unix ln command
ln is a standard Unix command used to create links (link) to files. Links allow more than one file
name to refer to the same file, elsewhere. There are two types of links, both of which are created
by ln:
1. symbolic links, which refer to a symbolic path indicating the abstract location of another
file
2. hard links, which refer to the specific location of physical data.
Symbolic link creation
A soft link, also called symbolic link, is a file that contains the name of another file. We can then
access the contents of the other file through that name. That is, a symbolic link is like a pointer to
the pointer to the file's contents. The function Prototype is
int symlink(const char *oldlink, const char *symlink);
How to create a symlink?
Use the ln command with the -s parameter.
$ ln -s fileA fileB
where fileA is the original file and fileB is the name you want to give to the symbolic link.
Now, let's take a look at these two objects with the ls command again:
$ ls -l
You can see that you get different result as compared to when we displayed the hard link.
The first difference between symlink and the original file is the inode number. The
inode is different for the original file and for the symbolic link.
There is the pipe symbol "l" before the permissions on the symlink line.
Has different permissions than the original file (because it is just a symbolic link).
The content of the symlink is just a string pointing to the original file.
The size of the symlink is not the same as the size of the original file. The symbolic link
is a separate entity and as such occupies some space on your hard drive.
Hard link creation
A hard link is essentially a label or name assigned to a file. It is possible to create a number of
different names that all refer to the same contents. A hard link is a pointer to the file's i-node.
How to create a hardlink?
Use the ln command to create a hard link.
$ ln fileA fileB
where fileA is the original file and fileB is the name you want to give to the hardlink. You have
the original file and one hard link that is attached to it.
Now, you look at these two objects with the ls command:
$ ls -l
You can see in the output of this command that both files fileA and fileB have the same inode
number. In addition to having the same inode, both files have the same file permissions and the
same size. Because that size is reported for the same inode, we can see that a hard link does
not occupy any extra space on your space.
#include <iostream>
using namespace std;
#include <sys/types.h>
#include <unistd.h>
#include <string.h>
int main (int argc, char* argv[])
{
char* buf[256], tname[256];
if ((argc< 3 && argc > 4) || (argc==4 && strcmp(argv[1],"-s")))
{
cout << "usage: " << argv[0] << " [-s] <orig_file> <new_link>\n";
return 1;
}
if (argc==4)
{
cout<<"Symbolic Link";
return symlink( argv[2], argv[3]); /* create a symbolic link */
}
else
return link(argv[1], argv[2]); /* create a hard link */
return 0;
}
/*OUTPUT for symbolic link */
[ksit@networklab1 ~]$ g++ 5B.CPP
[ksit@networklab1 ~]$ ./a.out -s 2.CPP a1.txt
Symbolic link
[ksit@networklab1 ~]$ ls -l
total 52
-rwxr-xr-x. 2 ksit ksit 680 Jan 22 16:17 1.CPP
-rw-r--r--. 1 ksit ksit 680 Jan 21 20:34 1.CPP~
-rwxr-xr-x. 1 ksit ksit 1284 Jan 21 16:47 2.CPP
-rwxr-xr-x. 1 ksit ksit 1321 Jan 21 16:53 3.CPP
-rwxr-xr-x. 1 ksit ksit 897 Jan 21 21:34 4.CPP
-rwxr-xr-x. 1 ksit ksit 669 Jan 22 16:47 5A.CPP
-rwxr-xr-x. 1 ksit ksit 542 Jan 22 16:50 5B.CPP
lrwxrwxrwx. 1 ksit ksit 5 Jan 22 17:05 a1.txt -> 2.CPP
-rwxrwxr-x. 1 ksit ksit 6423 Jan 22 17:04 a.out
drwxr-xr-x. 5 ksit ksit 4096 Jan 21 15:09 Desktop
-rwxr-xr-x. 2 ksit ksit 680 Jan 22 16:17 new1.txt
-rwxr-xr-x. 1 ksit ksit 220 Jan 21 17:29 sam.CPP
-rw-rw-r--. 1 ksit ksit 11 Jan 21 21:16 SAN.CPP
/*OUTPUT for hard link */
[ksit@networklab1 ~]$ g++ 5B.CPP
[ksit@networklab1 ~]$ ./a.out 3.CPP a2.txt
[ksit@networklab1 ~]$ ls -l
total 56
-rwxr-xr-x. 2 ksit ksit 680 Jan 22 16:17 1.CPP
-rw-r--r--. 1 ksit ksit 680 Jan 21 20:34 1.CPP~
-rwxr-xr-x. 1 ksit ksit 1284 Jan 21 16:47 2.CPP
-rwxr-xr-x. 2 ksit ksit 1321 Jan 21 16:53 3.CPP
-rwxr-xr-x. 1 ksit ksit 897 Jan 21 21:34 4.CPP
-rwxr-xr-x. 1 ksit ksit 669 Jan 22 16:47 5A.CPP
-rwxr-xr-x. 1 ksit ksit 542 Jan 22 16:50 5B.CPP
lrwxrwxrwx. 1 ksit ksit 5 Jan 22 17:05 a1.txt -> 2.CPP
-rwxr-xr-x. 2 ksit ksit 1321 Jan 21 16:53 a2.txt
-rwxrwxr-x. 1 ksit ksit 6423 Jan 22 17:04 a.out
drwxr-xr-x. 5 ksit ksit 4096 Jan 21 15:09 Desktop
-rwxr-xr-x. 2 ksit ksit 680 Jan 22 16:17 new1.txt
-rwxr-xr-x. 1 ksit ksit 220 Jan 21 17:29 sam.CPP
-rw-rw-r--. 1 ksit ksit 11 Jan 21 21:16 SAN.CPP
6. Write a C/C++ program to illustrate the race condition.
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. The fork function is a source
for it. Depends on whether the parent or child runs first after the fork. In general, we cannot
predict which process runs first. Even if we knew which process would run first, what happens
after that process starts running depends on the system load and the kernel's scheduling
algorithm. To avoid race conditions and to avoid polling, some form of signaling is required
between multiple processes. Various forms of inter process communication (IPC) can also be
used.
The below program outputs two strings: one from the child and one from the parent. The
program contains a race condition because the output depends on the order in which the
processes are run by the kernel and for how long each process runs. We set the standard output
unbuffered, so every character output generates a write. The goal in this example is to allow the
kernel to switch between the two processes as often as possible to demonstrate the race
condition.
#include<iostream>
#include<stdio.h>
#include<stdlib.h>
#include<sys/types.h>
#include<string.h>
#include<unistd.h>
using namespace std;
void charatatime(char *);
/*Here child will wait for the parent to finish its execution and then the
child continues*/
int main()
{
pid_t pid;
if ((pid=fork())<0)
{
cout<<"FORK ERROR\n" <<endl;
}
else
if (pid==0)
{
charatatime("output from child\n");
}
else
{
charatatime("output from parent\n");
}
exit(0);
}
void charatatime(char *str)
{
char *ptr;
int c;
setbuf(stdout,NULL);
for(ptr=str;(c=*ptr++)!=0;)
putc(c,stdout);
}
/*OUTPUT*/
output from parent
output from child
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.
“Zombie process or defunct process is a process that have completed the execution, have
released all the resources (CPU, memory) but still had an entry in the process table”. When a
process finishes execution, it will have an exit status to report to its parent process. Because of
this last little bit of information, the process will remain in the operating system’s process table
as a zombie process, indicating that it is not to be scheduled for further execution, but that it
cannot be completely removed (and its process ID cannot be reused) until it has been determined
that the exit status is no longer needed.
When a child exits, the parent process will receive a SIGCHLD signal to indicate that one of its
children has finished executing; the parent process will typically call the wait() system call at this
point. That call will provide the parent with the child’s exit status, and will cause the child to be
reaped, or removed from the process table.
Most of the time, the reason for existence of Zombie process is bad coding. Normally, when a
child (subprocess) finishes it’s task and exits, then it’s parent is suppose to use the “wait” system
call and get the status of the process. So, until the parent process don’t check for the child’s exit
status, the process is a Zombie process, but it usually is very small duration. But if due to any
reason (bad programming or a bug), the parent process didn’t check the status of the child and
don’t call “wait”, the child process stays in the state of Zombie waiting for parent to check it’s
status. To see if there are zombie processes on a system run “ps aux” and look for a Z in the
STAT column.
The ps (i.e., process status) command is used to provide information about the currently running
processes, including their process identification numbers (PIDs). system() executes a command
specified in string by calling /bin/sh -c string, and returns after the command has been
completed. sleep is a Unix command line program that suspends program execution for a
specified period of time.
#include<iostream>
#include<stdlib.h>
#include<unistd.h>
#include<errno.h>
#include<ctype.h>
#include<stdio.h>
#include<sys/types.h>
using namespace std;
#define PSCMD "ps -el|grep 'Z'"
int main(void)
{
pid_t pid;
if((pid=fork( ))<0)
cout<<"fork error";
else if(pid==0)
exit(0);
/*parent*/
sleep(4);
system(PSCMD);
exit(0);
}
output:
./a.out
F S UID PID PPID C PRI NI ADDR SZ WCHAN TTY TIME CMD
1 Z 501 2913 2912 0 80 0 - 0 ? pts/0 00:00:00 a.out <defunct>
8. Write a C/C++ program to avoid zombie process by forking twice.
A zombie process occupies a pid in the system, decrease the available pids in the system.
Zombies are mark as "defunct" while checking the process by the "ps" command. However,
sometimes we do not want the parent process to wait for its child process for a long time. There
is a way to achieve both "not create zombie process" and "not wait for the child process to its
termination", and the way is to do a double forkwhen a parent process (say A) want to fork a
child process to do "something". Process A does not fork a process to do "something" directly.
Process A first forks a child process (say B), and process then forks its child process (say C) to
do "something" and process B terminates as soon as process C is created. In this way, process A
only has to wait for process B for a short time. In the same time, since it has no parent process
(process B is dead), the system will "rechild" process C to the init process. The init process calls
wait() for its child process, solving the zombie process problem.
The child becomes a zombie after it terminates, if its parent is still
alive, and it doesn't call wait. Until the parent calls wait, or the
parent exits, and the child is inherited by init, the child remains a
zombie.By using the double-fork trick, the child (well, the grandchild really)
is immediately inherited by init, so it can never become a zombie (and
you can never retrieve its exit code either).When a process's parent terminates, the "init" process
takes over as its
parent. So when the child process exits, the grandchild loses its
parent, and is adopted by init. Init always reaps its dead children, so
they don't become zombies.
#include <iostream>
#include<stdio.h>
#include<unistd.h>
#include<stdlib.h>
using namespace std;
#include <sys/wait.h>
#include<errno.h>
int main(void)
{
pid_t pid;
if((pid=fork())<0)
{
printf("fork error");
}
else
if(pid == 0)
{
if((pid =fork())<0)
printf("fork error");
else
if(pid>0)
exit(0);
sleep(2);
printf("second child, parent pid = %d\n", getppid());
exit(0);
}
if(waitpid(pid, NULL,0)!=pid)
printf("waitpid error");
exit(0);
}
output:
./a.out
second child, parent pid = 1
9. Write a C/C++ program to implement the system function.
The below program prompts users to enter shell commands from standard input and executes
each command via the system function. The program terminates normally when either user
enters end-of-file(<ctrl-D>) at the shell prompt or the return status odf a System call is nonzero. The system function emulates the C library function system. The system function prototype is: int
system(const char *cmd);
Both functions invoke the Bourne shell(/bin/sh) to interpret and execute a shell command that is
specified via the argument cmd. A command may consist of simple shell command or a series of
shell commands separated by semicolons or pipes.
The system function calls fork to create a child process. The child process in turn, calls execlp to
execute a Bourne shell program(/bin/sh) with –c and cmd arguments. The –c option instructs the
Bourne shell to interpret and execute the cmd arguments as if they were entered at shell level.
After cmd is executed, the child process is terminated and the exit status of the Bourne shell is
passed to the parent process, which calls the system function.
Note that the system function calls waitpid to specifically wait for the child that it forked. This is
important, as the system function may be called by a process before that forked a child process
calling system; thus, the system function would wait only for child processes forked by it and not
those created by the calling process.
When the waitpid returns, the system function checks that1) the return PID matches that of the
child process that it forked; and (2) the child terminated via _exit. If both conditions are true, the
system function returns the child exit code. Otherwise a -1 to indicate failure status.
The exec collection of functions of Unix-like operating systems cause the running process to be
completely replaced by the program passed as an argument to the function. As a new process is
not created, the process identifier (PID) does not change, but the data, heap and stack of the
original process are replaced by those of the new process.
The functions are declared in the unistd.h header for the POSIX standard and in process.h for
DOS, OS/2, and Windows.
int execl(char const *path, char const *arg0, ...);
l - Command-line arguments are passed individually to the function.
path
The argument specifies the path name of the file to execute as the new process image.
Arguments beginning at arg0 are pointers to arguments to be passed to the new process image.
The argv value is an array of pointers to arguments.
arg0
The first argument arg0 should be the name of the executable file. Usually it is the same value as the path argument.
#include <iostream>
using namespace std;
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>
int System( const char *cmd)
// emulate the C system function
{
pid_t pid;
int status;
switch (pid=fork())
{
case -1: return -1;
case 0: execl("/bin/sh","sh","-c",cmd,0);
perror("execl");
exit(errno);
}
if (waitpid(pid,&status,0)==pid && WIFEXITED(status))
return WEXITSTATUS(status);
return -1;
}
int main()
{
int rc = 0;
char buf[256];
do
{
printf("sh> "); fflush(stdout);
if (!gets(buf)) break;
rc = System(buf);
} while (!rc);
return (rc);
}
output:
./a.out
sh> cal
January 2013
Su Mo Tu We Th Fr Sa
1 2 3 4 5
6 7 8 9 10 11 12
13 14 15 16 17 18 19
20 21 22 23 24 25 26
27 28 29 30 31
sh> date
Tue Jan 22 16:37:53 IST 2013
sh>ctrl d
10. Write a C/C++ program to set up a real-time clock interval timer using
the alarm API.
#include <stdio.h>
#include <unistd.h>
#include <signal.h>
void handler(int);
int main()
{
struct sigaction action;
sigemptyset(&action.sa_mask);
action.sa_handler = handler;
action.sa_flags = SA_RESTART;
if( sigaction( SIGALRM,&action,0 )==-1 )
{
perror( "sigaction");
return 1;
}
if (alarm(500) == -1)
{
perror("alarm" );
}
else
for(;;)
{
printf("System set\n");
}
return 0;
}
void handler(int signum)
{
alarm(500);
printf("Scheduled\n");
}
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).
/* Input to the program is assumed to be syntactically correct. The
expression of ‘if’ statement, statement for true condition and statement for
false condition are enclosed in parenthesis */
#include <stdio.h>
#include <stdlib.h>
#include <conio.h>
#include <string.h>
int parsecondition(char[],int,char*,int);
void gen(char [],char [],char[],int);
int main()
{
int counter = 0,stlen =0,elseflag=0;
char stmt[60]; // contains the input statement
char strB[54]; // holds the expression for 'if' condition
char strS1[50]; // holds the statement for true condition
char strS2[45]; // holds the statement for false condition
clrscr();
printf("Format of ‘if’ statement \n Example ...\n");
printf("if (a<b) then (s=a);\n");
printf("if (a<b) then (s=a) else (s=b);\n\n");
printf("Enter the statement \n");
gets(stmt);
stlen = strlen(stmt);
counter = counter + 2; // increment over 'if'
counter = parsecondition(stmt,counter,strB,stlen);
if(stmt[counter]==')')
counter++;
counter = counter + 3; // increment over 'then'
counter = parsecondition(stmt,counter,strS1,stlen);
if(stmt[counter+1]==';')
{ // reached end of statement, generate the output
printf("\n Parsing the input statement....");
gen(strB,strS1,strS2,elseflag);
getch();
return 0;
}
if(stmt[counter]==')')
counter++; // increment over ')'
counter = counter + 3; // increment over 'else'
counter = parsecondition(stmt,counter,strS2,stlen);
counter = counter + 2; // move to the end of statement
if(counter == stlen)
{ //generate the output
elseflag = 1;
printf("\n Parsing the input statement....");
gen(strB,strS1,strS2,elseflag);
getch();
return 0;
}
return 0;
}
/* Function : parsecondition
Description : This function parses the statement from the given index to get
the statement enclosed in () Input : Statement, index to begin search, string
to store the condition, total string length Output : Returns 0 on failure,
Non zero counter value on success*/
int parsecondition(char input[],int cntr,char *dest,int totallen)
{
int index = 0,pos = 0;
while(input[cntr]!= '(' && cntr <= totallen)
cntr++;
if(cntr >= totallen)
return 0;
index = cntr;
while (input[cntr]!=')')
cntr++;
if(cntr >= totallen)
return 0;
while(index<=cntr)
dest[pos++] = input[index++];
dest[pos]='\0'; //null terminate the string
return cntr; //non zero value
}
/* Function : gen ()
Description : This function generates three address code Input :
Expression, statement for true condition, statement for false condition,
flag to denote if the 'else' part is present in
the statement output :Three address code*/
void gen(char B[],char S1[],char S2[],int elsepart)
{
int Bt =101,Bf = 102,Sn =103;
printf("\n\tIf %s goto %d",B,Bt);
printf("\n\tgoto %d",Bf);
printf("\n%d: ",Bt);
printf("%s",S1);
if(!elsepart)
printf("\n%d: ",Bf);
else
{ printf("\n\tgoto %d",Sn);
printf("\n%d: %s",Bf,S2);
printf("\n%d:",Sn);
}
}
output:
Format of if statement
Example ...
if (a<b) then (s=a);
if (a<b) then (s=a) else (s=b);
Enter the statement
if (a<b) then (s=a);
Parsing the input statement....
100:If (a<b) goto 102
101:goto 103
102: (s=a)
103: s.next
output:
Format of if statement
Example ...
if (a<b) then (s=a);
if (a<b) then (s=a) else (s=b);
Enter the statement
if (a<b) then (s=a) else (s=b);
Parsing the input statement....
100:If (a<b) goto 102
101:goto 104
102: (s=a)
103:goto 105
104: (s=b)
105 s.next:
12. Write a yacc program that accepts a regular expression as input and produce its parse
tree as output.
//yacc program
%{
#include<ctype.h>
char str[20];
int i=0;
%}
%token id
%left '+''/''*''-'
%%
E:S {infix_postfix(str);}
S:S'+'T
|S'-'T
|T
;
T:T'*'F
| T'/'F
|F
;
F:id
|'('S')'
|
;
%%
//C Program
#include<stdio.h>
main()
{
printf("\nEnter an identifier:");
yyparse();
}
yyerror()
{
printf("invalid");
}
yylex(){
char ch=' ';
while(ch!='\n'){
ch=getchar();
str[i++]=ch;
if(isalpha(ch)) return id;
if(ch=='+'||ch=='*'|| ch=='-'||ch=='/') return ch;}
str[--i]='\0';
return(0);
exit(0);
}
void push(char stack[],int *top,char ch)
{
stack[++(*top)]=ch;
}
char pop(char stack[],int *top)
{
return(stack[(*top)--]);
}
int prec(char ch)
{ switch(ch)
{
case '/':
case '*': return 2;
case '+':
case '-': return 1;
case '(': return 0;
default: return -1;
}
}
void infix_postfix(char infix[])
{
int top=-1,iptr=-1,pptr=-1;
char postfix[20],stack[20],stksymb,cursymb;
push(stack,&top,'\0');
while((cursymb=infix[++iptr])!='\0')
{
switch(cursymb)
{ case '(': push(stack,&top,cursymb);
break;
case ')': stksymb=pop(stack,&top);
while(stksymb!='(')
{
postfix[++pptr]=stksymb;
stksymb=pop(stack,&top);
}
break;
case '*':
case '/':
case '+':
case ‘-‘:while(prec(stack[top])>=prec(cursymb))
postfix[++pptr]=pop(stack,&top);
push(stack,&top,cursymb);break;
default: if(isalnum(cursymb)==0)
{printf("Error in input!"); exit(0);}
postfix[++pptr]=cursymb;
}