unix systems programming and compiler design laboratory - 10csl67

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

Upload: vivek-ranjan

Post on 27-Oct-2015

621 views

Category:

Documents


1 download

DESCRIPTION

For VTU

TRANSCRIPT

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:

Work on the Following list of manifested constant supported by pathconf 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;

}

}

while(top!=-1)

postfix[++pptr]=pop(stack,&top);

printf("%s\n",postfix);

}

Input Output

a+b*c/d abc*d/+

(a+b)*c/d ab+c*d/