Beruflich Dokumente
Kultur Dokumente
Introduction:-
Posix record locking is called advisory locking. This means the kernel
maintains correct knowledge of all files that have been locked by each
process, but it does not prevent a process from writing to a file that is read-
locked by another process. Similarly, the kernel does not prevent a process
from reading from a file that is write-locked by another process. A process
can ignore an advisory lock and write to a file that is read-locked, or read
from a file that is write-locked, assuming the process has adequate
permissions to read or write the file. Advisory locks are fine for cooperating
processes.
Mandatory locking
Some systems provide another type of record locking, called
mandatory locking. With a mandatory lock, the kernel checks every read and
write request to verify that the operation does not interfere with a lock held
by a process. For a normal blocking descriptor, the read or write that
conflicts with a mandatory lock puts the process to sleep until the lock is
released. To enable mandatory locking for a particular file,
• the group-execute bit must be off, and
• the set-group-ID bit must be on.
File locking vs record locking
File locking locks entire file. Record locking is used to describe the ability of
a process to prevent other processes from modifying a region of a file, while
the first process is reading or modifying that portion of the file.
Unix kernel does not have a notion of records in a file. A better term is
“range locking “, since it is a range of a file that is locked
System V provides the following locking function for file and record locking
4.2 BSD provides the following locking function for file locking
#include <sys/file.h>
int flock(int fd, int operation);
Apply or remove an advisory lock on the open file specified by fd. The
parameter operation is one of the following:
LOCK_SH Place a shared lock. More than one process may hold a
shared lock for a given file at a given time.
LOCK_EX Place an exclusive lock. Only one process may hold an
exclusive lock for a given file at a given time.
LOCK_UN Remove an existing lock held by this process.
#include <unistd.h>
Two file descriptors are returned through the filedes argument: filedes[0] is
open for reading, and filedes[1] is open for writing. The output of filedes[1]
is the input for filedes[0].
A pipe in a single process is useless. Normally, the process that calls pipe
then calls fork, creating an IPC channel from the parent to the child or vice
versa. Following shows this scenario.
What happens after the fork depends on which direction of data flow we
want. For a pipe from the parent to the child, the parent closes the read end
of the pipe (fd[0]), and the child closes the write end (fd[1]). Following
figure shows the resulting arrangement of descriptors.
Pipe from parent to child
For a pipe from the child to the parent, the parent closes fd[1], and the
child closes fd[0].
When one end of a pipe is closed, the following two rules apply.
1.If we read from a pipe whose write end has been closed, read returns 0 to
indicate an end of file after all the data has been read.
2.If we write to a pipe whose read end has been closed, the signal SIGPIPE
is generated. If we either ignore the signal or catch it and return from the
signal handler, write returns 1 with errno set to EPIPE.
Following is the code to create a pipe between a parent and its child and to
send data down the pipe.
When a two way flow of data is desired, we must create two pipes and use
one for each direction. The actual steps are
char recvbuff[100],sendbuff[100];
pipe(p1fd);
pipe(p2fd);
if((childpid = fork()) == -1)
{
perror("fork");
exit(1);
}
if(childpid == 0) /*Server*/
{
close(p1fd[1]);
close(p2fd[0]);
server(p1fd[0],p2fd[1]);
}
else /*Client*/
{
close(p2fd[1]);
close(p1fd[0]);
client(p2fd[0],p1fd[1]);
return(0);
}
while(1)
{
nbytes=read(readfd, recvbuff, sizeof(recvbuff));
for(i=0;i<nbytes;i++)
sendbuff[i]=toupper(recvbuff[i]);
Limitations of pipes:
While pipes are widely used in the UNIX environment, unnamed pipes are
the most commonly used form of IPC—they suffer from two major
drawbacks.
1. They are half-duplex in nature—data flows only in one direction at a time.
2. They can be used between directly related processes. i.e the processes
must have a parent-child relationship. A pipe is created by a process that
then calls fork( ), and The pipe is used between parent and child.
FIFOs (Named Pipes):
A FIFO ("First In, First Out") is sometimes known as a named pipe. That is,
it's like a pipe, except that it has a name! In this case, the name is that of a
file that multiple processes can open() and read and write to.
This latter aspect of FIFOs is designed to let them get around one of the
shortcomings of normal pipes: you can't grab one end of a normal pipe that
was created by an unrelated process. See, if I run two individual copies of a
program, they can both call pipe() all they want and still not be able to
speak to one another. (This is because you must pipe(), then fork() to get a
child process that can communicate to the parent via the pipe.) With FIFOs,
though, each unrelated process can simply open() the pipe and transfer data
through it.
Since the FIFO is actually a file on disk, you have to do some fancy-
schmancy stuff to create it. It's not that hard. You just have to call mknod()
with the proper arguments. Here is a mknod() call that creates a FIFO:
In the above example, the FIFO file will be called "myfifo". The second
argument is the creation mode, which is used to tell mknod() to make a
FIFO (the S_IFIFO part of the OR) and sets access permissions to that file
(octal 644, or rw-r--r--) which can also be set by ORing together macros
from sys/stat.h. This permission is just like the one you'd set using the
chmod command. Finally, a device number is passed. This is ignored when
creating a FIFO, so you can put anything you want in there.
(An aside: a FIFO can also be created from the command line using the Unix
mknod command.)
Once the FIFO has been created, a process can start up and open it for
reading or writing using the standard open() system call.
The following are two programs which will send data through a FIFO. One is
speak.c which sends data through the FIFO, and the other is called tick.c, as
it sucks data out of the FIFO.
Here is speak.c:
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>
#include <fcntl.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#define FIFO_NAME "american_maid"
int main(void)
{
char s[300];
int num, fd;
What speak does is create the FIFO, then try to open() it. Now, what will
happen is that the open() call will block until some other process opens the
other end of the pipe for reading. (There is a way around this—see
O_NDELAY, below.) That process is tick.c, shown here:
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>
#include <fcntl.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#define FIFO_NAME "american_maid"
int main(void)
{
char s[300];
int num, fd;
mknod(FIFO_NAME, S_IFIFO | 0666, 0);
printf("waiting for writers...\n");
fd = open(FIFO_NAME, O_RDONLY);
printf("got a writer\n");
do {
if ((num = read(fd, s, 300)) == -1)
perror("read");
else {
s[num] = '\0';
printf("tick: read %d bytes: \"%s\"\n", num, s);
}
} while (num > 0);
return 0;
}
Like speak.c, tick will block on the open() if there is no one writing to the
FIFO. As soon as someone opens the FIFO for writing, tick will spring to life.
Two way communication using named pipes
1. A read requesting less than is in the pipe or FIFO returns only the
requested amount of data.
2. If a process asks to read more data than currently available in the pipe
or FIFO only the data available is returned.
3. If there is no data in the pipe or FIFO, and if no processes have it open
for writing, a read returns zero signifying the end of file.
4. If a process writes less than the capacity of a pipe of FIFO the write is
guaranteed to be atomic.
5. If the process writes to a pipe or FIFO, but there are no processes in
existence that have it open for reading, the SIGPIPE signal is
generated, and write returns zero with errno set to EPIPE.
The pipes and FIFOs, uses the stream I/O model, which is natural for
Unix. No record boundaries exist- reads and writes do not examine the data
at all. A process that reads 100 bytes from a pipe, for example, cannot tell
whether the process that wrote the data into the pipe did a single write of
100 bytes, five writes of 20 bytes, two writes of 50 bytes, or some other
combination of writes that totals 100 bytes. The data is a byte stream with
no interpretation done by the system. If any interpretation is desired, the
writing process and the reading process must agree to it a priori and do it
themselves.
Name Spaces:
Pipes do not have names but FIFOs have a unix path name to identify
them. The set of possible names for a given type of IPC is called its name
space. The name space is important because for all forms of IPC other than
plain pipes, the name is how the client and server “connect to exchange
messages.
Following table summarizes the naming conventions used by different
forms of IPC.
#include <sys/types.h>
#include <sys/ipc.h>
key_k ftok(char *pathname, char proj);
An IPC application should have the both server and clients all agree on a
single path name that have some meaning to the application.
If the path name does not exists ftok returns -1
proj argument is an 8- bit character.
SYSTEM V IPC
The kernel maintains a structure of information for each IPC object, similar
to the information it maintains for files.
Message Queues
msgget Function
The return value is an integer identifier that is used in the other three
msg functions to refer to this queue, based on the specified key, which can
be a value returned by ftok or the constant IPC_PRIVATE. oflag is a
combination of the read-write permission values.
struct msgbuff
{
long mtype; /*message type , must be greater than zero*/
int len; /*actual length of data*/
char mtext[1]; /*Message data*/
}
The message type must be greater than 0, since nonpositive message types
are used as a special indicator to the msgrcv function.
msgrcv Function
msgctl Function
IPC_SET changes the properties of the queue such as queue size etc.
Multiplexing Messages
Two features are provided by the type field that is associated with each
message on a queue:
1.The type field can be used to identify the messages, allowing multiple
processes to multiplex messages onto a single queue. One value of the
type field is used for messages from the clients to the server, and a
different value that is unique for each client is used for messages from
the server to the clients. Naturally, the process ID of the client can be
used as the type field that is unique for each client.
2.The type field can be used as a priority field. This lets the receiver read
the messages in an order other than first-in, first-out (FIFO). With pipes
and FIFOs, the data must be read in the order in which it was written.
With System V message queues, we can read the messages in any order
that is consistent with the values we associate with the message types.
for(i=1;i<=20;i++)
{
msg->mtype=(i%3)+1;
sprintf(msg->mtext,"hello world-%d",i);
msgsnd(queue_id,msg,strlen(msg->mtext)+1,0);
}
free(msg);
printf("generated 20 messages");
return 0;
}
/*messageq reader program */
#include<string.h>
#include<stdio.h>
#include<stdlib.h>
#include<sys/types.h>
#include<sys/ipc.h>
#include<sys/msg.h>
struct msgbuf{
long mtype;
char mtext[1];
};
int main(int argc, char *argv[])
{
int queue_id;
struct msgbuf *msg;
int msg_type;
if(argc !=2)
{
printf("Usage: %s <message type>\n",argv[0]);
exit(0);
}
msg_type=atoi(argv[1]);
if(msg_type <1 || msg_type >3)
{
printf("Message type must be between 1 and 3\n");
exit(0);
}
queue_id=msgget((key_t)999,0);
printf("Message queue opened, queue id '%d'.\n",queue_id);
msg=(struct msgbuf*)malloc(sizeof(struct msgbuf)+200);
while(1)
{
msgrcv(queue_id,msg,200,msg_type,0);
printf("Reader '%d' read message: '%s'\n",msg_type,msg->mtext);
sleep(1);
}
return 0;
}
Semaphores
For every set of semaphores in the system, the kernel maintains the
following structure of information, defined by including <sys/sem.h >:
semget Function
#include <sys/sem.h>
The return value is an integer called the semaphore identifier that is used
with the semop and semctl functions.
The nsems argument specifies the number of semaphores in the set. If
we are not creating a new semaphore set but just accessing an existing set,
we can specify this argument as 0. We cannot change the number of
semaphores in a set once it is created.
The oflag value is a combination of the permissions. This can be bitwise-
ORed with either IPC_CREAT or IPC_EXCL.
semop Function
#include <sys/sem.h>
int semctl(int semid, int semnum, int cmd, union semun arg);
union semun {
int val; /* for SETVAL */
struct semid_ds *buf; /* for IPC_STAT and IPC_SET */
unsigned short *array; /* for GETALL and SETALL */
};
IPC_STAT Fetch the semid_ds structure for this set, storing it in the
structure pointed to by arg.buf.
IPC_SET Set the sem_perm.uid, sem_perm.gid, and sem_perm.mode fields
from the structure pointed to by arg.buf in the semid_ds structure
associated with this set.
IPC_RMID Remove the semaphore set from the system. This removal is
immediate. Any other process still using the semaphore will get
an error of EIDRM on its next attempted operation on the
semaphore.
GETVAL Return the value of semval for the member semnum.
SETVAL Set the value of semval for the member semnum. The value is
specified by arg.val.
GETPID Return the value of sempid for the member semnum.
IPC_STAT Fetch the semid_ds structure for this set, storing it in the
structure pointed to by arg.buf.
GETNCNT Return the value of semncnt for the member semnum.
GETZCNT Return the value of semzcnt for the member semnum.
GETALL Fetch all the semaphore values in the set. These values are stored
in the array pointed to by arg.array.
SETALL Set all the semaphore values in the set to the values pointed to
by arg.array.
For all the GET commands other than GETALL, the function returns the
corresponding value. For the remaining commands, the return value is 0.
#include<sys/types.h>
#include<sys/ipc.h>
#include<sys/sem.h>
#include<stdio.h>
#include<fcntl.h>
#include<unistd.h>
#include<string.h>
#define SEQFILE "seqno.c"
#define MAXBUFF 500
static struct sembuf op_lock[2]={{0,0,0},{0,1,0}};
static struct sembuf op_unlock[1]={0,-1,IPC_NOWAIT};
int semid=-1;
main()
{
void my_lock(int);
void my_unlock(int);
int fd,i,n,pid,seqno=0;
char buff[MAXBUFF];
pid=getpid();
fd=open(SEQFILE,O_CREAT|O_RDWR);
for(i=1; i<=20; i++)
{
my_lock(fd);
lseek(fd,0L,0);
n=read(fd,buff,MAXBUFF);
buff[n]='\0';
n=sscanf(buff,"%d",&seqno);
printf("pid= %d,seq# = %d\n",pid,seqno);
seqno++;
sprintf(buff,"%d",seqno);
n=strlen(buff);
lseek(fd,0L,0);
write(fd,buff,n);
my_unlock(fd);
}
}
void my_lock(int fd)
{
if(semid<0)
{
if((semid=semget((key_t)12345,1,IPC_CREAT|0666))<0)
perror("semget error");
}
if(semop(semid,&op_lock[0],2)<0)
perror("semop lock error");
}
void my_unlock(int fd)
{
if(semop(semid,&op_unlock[0],1)<0)
perror("semop unlock error");
}
[root@rgmcse ~]# gcc locksem.c
[root@rgmcse ~]# ./a.out& ./a.out&
[1] 3239
[2] 3240
pid= 3239,seq# = 14 pid= 3240,seq# = 29
pid= 3240,seq# = 0 pid= 3240,seq# = 15 pid= 3239,seq# = 30
pid= 3240,seq# = 1 pid= 3239,seq# = 16 pid= 3240,seq# = 31
pid= 3240,seq# = 2 pid= 3240,seq# = 17 pid= 3239,seq# = 32
pid= 3240,seq# = 3 pid= 3239,seq# = 18 pid= 3239,seq# = 33
pid= 3240,seq# = 4 pid= 3240,seq# = 19 pid= 3239,seq# = 34
pid= 3240,seq# = 5 pid= 3239,seq# = 20 pid= 3239,seq# = 35
pid= 3240,seq# = 6 pid= 3240,seq# = 21 pid= 3239,seq# = 36
pid=3240,seq# =7 pid= 3239,seq# = 22 pid= 3239,seq# = 37
pid= 3239,seq# = 8 pid= 3240,seq# = 23 pid= 3239,seq# = 38
pid= 3240,seq# = 9 pid= 3239,seq# = 24 pid= 3239,seq# = 39
pid= 3239,seq# = 10 pid= 3240,seq# = 25
pid= 3240,seq# = 11 pid= 3239,seq# = 26
pid= 3239,seq# = 12 pid= 3240,seq# = 27
pid= 3240,seq# = 13 pid= 3239,seq# = 28