Archive

Archive for the ‘C’ Category

REMOTE PROCEDURE CALLs: C vs. Erlang (part 2)

December 21, 2009 Leave a comment

In the previous post, I have told you how to make a counter server in C and then call its functionalities through RPC calls.

In Erlang it is defined a module called rpc. It provides services which are similar to remote procedure calls. It also contains broadcast facilities and parallel evaluators. As I told you also in the previous blog a remote procedure call is a method to call a function on a remote node and collect the answer.

The most important function in this module is rpc:call(Node, Module, Function, Arguments), it executes the specific function defined in the specific module, with the specified arguments on the node Node and returns the corresponding that can be either the return value, or a tupla of the form {badrpc, Reason}.

Sounds easy! Thus let’s start by coding our counter server:

%%%-------------------------------------------------------------------
%%% File    : counter_svr.erl
%%% Author  : XXXXX
%%% Description : A counter server
%%%
%%% Created : 19 Dec 2009 by XXXXX
%%%-------------------------------------------------------------------
-module(counter_svr).

-behaviour(gen_server).

%% API
-export([start_link/0,count/0]).

%% gen_server callbacks
-export([init/1, handle_call/3, handle_cast/2, handle_info/2,
	 terminate/2, code_change/3]).

-record(state, {counter=0}).

%%====================================================================
%% API
%%====================================================================
%%--------------------------------------------------------------------
%% Function: start_link() -> {ok,Pid} | ignore | {error,Error}
%% Description: Starts the server
%%--------------------------------------------------------------------
start_link() ->
    gen_server:start_link({local, ?MODULE}, ?MODULE, [], []).

%%====================================================================
%% gen_server callbacks
%%====================================================================

%%--------------------------------------------------------------------
%% Function: init(Args) -> {ok, State} |
%%                         {ok, State, Timeout} |
%%                         ignore               |
%%                         {stop, Reason}
%% Description: Initiates the server
%%--------------------------------------------------------------------
init([]) ->
    {ok, #state{}}.

%%--------------------------------------------------------------------
%% Function: %% handle_call(Request, From, State) -> {reply, Reply, State} |
%%                                      {reply, Reply, State, Timeout} |
%%                                      {noreply, State} |
%%                                      {noreply, State, Timeout} |
%%                                      {stop, Reason, Reply, State} |
%%                                      {stop, Reason, State}
%% Description: Handling call messages
%%--------------------------------------------------------------------
handle_call(count, _From, State) ->
    Reply = State#state.counter + 1,
    NewState = #state{counter=Reply},
    {reply, Reply, NewState};

handle_call(_Request, _From, State) ->
    Reply = ok,
    {reply, Reply, State}.

%%--------------------------------------------------------------------
%% Function: handle_cast(Msg, State) -> {noreply, State} |
%%                                      {noreply, State, Timeout} |
%%                                      {stop, Reason, State}
%% Description: Handling cast messages
%%--------------------------------------------------------------------
handle_cast(_Msg, State) ->
    {noreply, State}.

%%--------------------------------------------------------------------
%% Function: handle_info(Info, State) -> {noreply, State} |
%%                                       {noreply, State, Timeout} |
%%                                       {stop, Reason, State}
%% Description: Handling all non call/cast messages
%%--------------------------------------------------------------------
handle_info(_Info, State) ->
    {noreply, State}.

%%--------------------------------------------------------------------
%% Function: terminate(Reason, State) -> void()
%% Description: This function is called by a gen_server when it is about to
%% terminate. It should be the opposite of Module:init/1 and do any necessary
%% cleaning up. When it returns, the gen_server terminates with Reason.
%% The return value is ignored.
%%--------------------------------------------------------------------
terminate(_Reason, _State) ->
    ok.

%%--------------------------------------------------------------------
%% Func: code_change(OldVsn, State, Extra) -> {ok, NewState}
%% Description: Convert process state when code is changed
%%--------------------------------------------------------------------
code_change(_OldVsn, State, _Extra) ->
    {ok, State}.

%%--------------------------------------------------------------------
%%% Internal functions
%%--------------------------------------------------------------------
count() ->
    gen_server:call(?MODULE, count).

As you can see I used the template of a gen_server for my implementation of the counter server. The value of the counter is stored inside its state, and it starts with the value of 0. The server handles a specific call that is aimed to retrieve the counter from the server, increment it by one and return that value to the client.  In the internal functions space, I defined a function named count that performs the specific call to the gen_server.

Easy, isn’t it?

Now we have only to start the server in a Erlang node (we name it server@ourlocalhost).

bellerofonte@pegaso:~$ erl -sname server@pegaso
Erlang R13B02 (erts-5.7.3)
Eshell V5.7.3  (abort with ^G)
(server@pegaso)1> counter_svr:start_link().
{ok,<0.40.0>}

At this point the server is up an running on our Erlang node, so the only thing left is to start another Erlang node (or many nodes) and perform some RPC from these. First of all we start the second node, you should know that this node must be connected to the previous one and share with it the same cookie, if you don’t see Erlang User Guide for more info. In brief I can tell you that you can check whether two nodes are connected by calling in one node the function net:ping(‘othernode@host’) that gives back a ‘pong’ if they are connected, or a ‘pang’ if they are not…..moreover you can check if the nodes have the same cookie by calling in both nodes the function erlang:get_cookie() (otherwise you can set the cookie with erlang:set_cookie(Cookie). Coming back to our discussion on rpc, inside the second node we call the function through RPC by using as stated above rpc:call(Node, Module, Function, Arguments)…in our specific case Node is the node where the server is running, Module is the module implementing the server, Function is the function that retrieves the counter and increments it and Arguments is an empty list, since the function takes no arguments.

bellerofonte@pegaso:~$ erl -sname client@pegaso
Erlang R13B02 (erts-5.7.3)
Eshell V5.7.3  (abort with ^G)
(client@pegaso)1> rpc:call('server@pegaso', counter_svr, count, []).
1
(client@pegaso)2> rpc:call('server@pegaso', counter_svr, count, []).
2
(client@pegaso)3> rpc:call('server@pegaso', counter_svr, count, []).
3
(client@pegaso)4> rpc:call('server@pegaso', counter_svr, count, []).
4

As you may see in the example I posted above, multiple RPCs to our server, give back the result expected…..

In my opinion this comparison explains why I still prefer using Erlang when talking or coding distributed systems!

Categories: C, English, Erlang Tags: , ,

REMOTE PROCEDURE CALLs: C vs. Erlang (part 1)

December 21, 2009 Leave a comment

In the last weeks I have been geeking a while with Remote Procedure Calls.

RPCs were invented at Sun Microsystems in the 80s, and can be seen as “normal” procedure calls on a distributed system. The concept behind RPCs is the following: you don’t have to declare the function you want to use in your client, you can declare that function on a server and than if you invoke it, a so-called “middleware” is going to forward the request from the client to the server, where the function is executed and the result is returned back to the client.

Since I’m still confident that Erlang is much more better than C for distributed systems I’ll try to code in both languages a server that keeps a counter in it, each time a client calls via RPC a function it will increment that value.

In C I could do it coding three files. Let’s start with the first: a file (counter.x) with the specifications of the service provided by the functions that can be called remotely. The specifications must be converted to stubs and header files for the middleware using the tool rpcgen (we will se it later).

When you write the specifications, you must identify the functions that are going to be called remotely. The functions must specify the return type and arguments. For every function (called RPC), you must assign an ID and must include it in a program that must be uniquely referred to by an ID and a version.

Our counter.x may look like this:

program COUNTERPROG {
  version COUNTERVERS {
    int COUNTER() = 1;
  } = 1;
} = 0x20000001;

where the remote procedure COUNTER has ID 1 and is included in the version 1 of the program COUNTERPROG. The ID of the program is 0x20000001.

After that you must write the procedure code as if you would write it locally, but with slight changes. The procedure must use a conventional name PROGRAM_VERSION_svc (in our case counter_1_svc). The procedure uses pointers to return the result and receive the arguments.
One more argument is required to identify the execution context.

Thus in our case we can create a file named counter_proc.c of the following form:

#include <stdio.h>
#include "counter.h"
int *counter_1_svc(void *msg, struct svc_req *req)
{
  static int result = 0;
  result++;
  return (&result);
}

The header file counter.h is one of the files generated by rpcgen. As you can see we create a STATIC variable result that lives in the space allocated for static variables so that it can survive when the execution of the fuction ends. Whenever the function is called we increment that value and return the address of that static variable.

Now we have to write to client! This client, before performing the remote call to the server, must connect to it. Then, after the call it can disconnect. The connection is done using the function clnt_create(server address, program id, program version, transport protocol). The disconnection is done using the function clnt_destroy().

Let’s create a file called rcounter.c of the following form:

#include <stdio.h>
#include <string.h>
#include "counter.h"
int main(int argc, char **argv)
{
  CLIENT *clnt;
  int *result;
  char *server;
  // try to get the server address from command line
  if (argc != 2) {
    fprintf(stderr, "Usage is the following: %s server\n", argv[0]);
    return -1;
  }
  server = argv[1];
  // create the client
  clnt = clnt_create(server, COUNTERPROG, COUNTERVERS, "udp"); //udp may be set to tcp
  if (clnt == NULL) {
    clnt_pcreateerror(server);
    return -1;
  }
  // call the function
  result = counter_1(NULL, clnt);
  if (result == NULL) {
    clnt_perror(clnt, server);
    return -1;
  }
  // print the value obtained from the call
  printf("The value of the counter is %d\n", *result);
  // destroy the client
  clnt_destroy(clnt);
  return 0;
}

Now let’s compile all the files:

bellerofonte@pegaso:~$ rpcgen counter.x

This generates: counter.h counter_svc.c counter_clnt.c

bellerofonte@pegaso:~$ gcc counter_svc.c counter_proc.c -o counter_svr -lnsl
bellerofonte@pegaso:~$ gcc counter_clnt.c rcounter.c -o counter_clt -lnsl

and execute them:

bellerofonte@pegaso:~$ ./counter_svr &
[1] 4898
bellerofonte@pegaso:~$ ./counter_clt localhost
Counter is 1
bellerofonte@pegaso:~$ ./counter_clt localhost
Counter is 2
bellerofonte@pegaso:~$ kill -9 4898

As you can see at every call the value returned is the previous one plus 1 (starting from zero).

I killed the server in the end because I started it in background mode…if you want you can run the two files in different shells to avoid this.

Ok, this post starts being long…so for the Erlang code I will open a new one 🙂

Categories: C, English, Erlang Tags: , , ,

A client for our echo server in C

December 1, 2009 Leave a comment

Last time I posted something about sockets, ending up with a small server that accepted one client and greeted it.

Anyhow I left two things to do: fist the client was simulated by a telnet connection, and second the server wasn’t devised for a multiclient approach.

For the client side, I can briefly say that all the concepts I have introduced in last post are valid: to create a client we create a socket descriptor, connect to it and then send and receive data over it (we don’t use listen and accept here), then we close the socket.

Thus a valid example is:

#include <stdio.h>
#include <stdlib.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <string.h>

#define BUF_SIZE 	(1000)

int main(int narg, char *varg[])
{
    int socketfd;
    struct sockaddr_in server_addr;
    int port = atoi(varg[2]); //convert a string to an integer
    int recv_len, sent_len;
    char buf[BUF_SIZE];

    printf("Connecting to %s:%d\n", varg[1], port);

    // create the socket if possible
    if ((socketfd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP)) < 0) {
        perror("unable to create socket");
        return -1;
    }

    // configure the server address for the socket
    server_addr.sin_family = AF_INET;
    server_addr.sin_port = htons(port);
    server_addr.sin_addr.s_addr = inet_addr(varg[1]);

    // connect to the server via the socket created above
    if (connect(socketfd,
                   (struct sockaddr *) &server_addr, sizeof(server_addr)) < 0) {
        perror("unable to connect");
        return -1;
    }

    // receive data from server
    recv_len = recv(socketfd, buf, BUF_SIZE, 0);
    if (recv_len == 0) {
        printf("server disconnected!\n");
        close(socketfd);
        return 0;
    }
    if (recv_len < 0) {
        perror("error receiving data from server");
        return -1;
    }

    printf("received from server %d bytes:%s", recv_len, buf);

    close(socketfd);
    return 0;
}

To execute the following code try to run the run in one shell the code for the server I have posted last time and in another the following:

bellorofonte@pegaso:~$ ./client 127.0.0.1 4000
Connecting to 127.0.0.1:4000
received from server 14 bytes:Hello World!

You may have noticed that in the above case we specified the IP address of the server in the notation xxx.xxx.xxx.xxx, you may want to use the function struct hostent *gethostbyname(const char *name) by including the library <netdb.h>
You can type something like:

struct hostent *host = gethostbyname("localhost");

where hostent is the following structure:

struct hostent {
    char    *h_name;        // official name of host
    char    **h_aliases;    // list of aliases
    int     h_addrtype;     // host address type
    int     h_length;       // lenght of address
    char    **h_addr_list;  // list of addresses
}

For the multiclient server stay tuned!

Categories: C, English Tags: , , ,

Echo server in C

November 30, 2009 Leave a comment

In order to create an echo server we must introduce the concept of socket in C.

Sockets can be seen as an endpoint for cummunication, and in C are created by including in our code the following libraries:

<sys/types.h>
<sys/socket.h>

and by using the function int socket(int domain, int type, int protocol)

Let’s explain briefly the three variables used in the previous function:

int domain represents a communication domain, the protocol family to be used for communication (common values are AF_UNIX for local communication, AF_INET for IPv4 Internet protocols and AF_INET6 for IPv6).

int type specifies the communication semantics (commonly we use SOCK_STREAM that provides sequenced, reliable, two-way, connection-based byte streams but can be used other types as SOCK_RAW which provides raw network protocol access).

int protocol indicates a particular protocol for the socket, normally only a single protocol exists for supporting a particular socket type in a family but in some cases there can be more than one and this variable specifies which one should be used.

Function int socket(…) returns a socket descriptor if valid, otherwise it returns -1 and a errno to handle the error. To send or receive data (e.g to/from another process or machine) the socket must be connected to another socket.

Since we want to create a server first, we must introduce a way to setup a port to listen to. We do this by using the function int bind(int sockfd, struct sockaddr *addr, socklen_t addrlen) (the same libraries as before must have included). This function assigns the address specified by variable addr to the socket assigned to socket descriptor sockfd. addrlen specifies the size in bytes of the address structure pointed to by addr.

The struct sockaddr is structure representing an IP socket address, thus a pain (IP, PORT). This structure may change based on protocol you use, but is something like:

struct sockaddr {
	short sin_family;         // e.g. AF_INET for IPv4
	unsigned short sin_port;  // e.g. htons(4320) see htons()
	struct in_addr sin_addr;  // structure containing the IP address
	char sin_zero[8];
};

where:

in_addr {
	unsigned long s_addr;
};

If successful, bind() returns 0; otherwise it returns -1 and sets errno to indicate the error.
After binding, listening operation can be started as: listen(int sockefd, in queue) where socketfd is our socket descriptor and queue is the maximum number of connections pending. If successful, listen() returns 0; otherwise it returns -1 and sets errno to indicate the error.

To accept a connection we use the function int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen) which is used with connection based sockets such as streams. Argument sockfd is a socket that has been created with socket(), bound to a local address with bind(), and is listening for connections after a listen(). The argument addr is a pointer to a sockaddr structure filled in with the address of the peer socket, as known to the communications layer. The addrlen argument is a value-result argument: the caller must ini tialize it to contain the size (in bytes) of the structure pointed to by addr; on return it will contain the actual size of the peer address. If an error occurs, -1 is returned and errno is set to indicate the cause.

For exchanging data we use two functions: ssize_t send(int sockfd, const void *buf, size_t len, int flags) where buf is the message you want to send, flags is usually set to 0 and the function ssize_t recv(int sockfd, void *buf, size_t len, int flags). These functions return -1 in case of an error.

Finally to close a connection we include library and call int close(int sockfd)

Here an example of a server that waits for a connection and greets the user connected. (note that I did not check the return value of the functions):

#include <sys/socket.h>
#include <sys/types.h>
#include <string.h>
#include <unistd.h>

#define MAX_CONNECTIONS    (1000)
#define MAX_LINE           (1000)

int main()
{
  int sockfd;
  sockfd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);

  struct sockaddr_in my_addr;
  my_addr.sin_family = AF_INET;
  my_addr.sin_addr.s_addr = htonl(INADDR_ANY);
  my_addr.sin_port = htons(4000);

  bind(sockfd, (struct sockaddr *) &my_addr, sizeof(my_addr));
  listen(sockfd, MAX_CONNECTIONS);

  while(1)
    {
      struct sockaddr_in client_addr;
      int client_len = sizeof(client_addr);

      int client_fd = accept(sockfd, (struct sockaddr *) &client_addr,  &client_len);

      char buf[MAX_LINE];
      strcpy(buf, "Hello user!\n");
      send(client_fd, buf, strlen(buf) + 1, 0);
      close(client_fd);
    }
}

To run the server type in one shell the following:

bellerofonte@pegaso:~$ ./echoserv

and in another:

bellerofonte@pegaso:~$ telnet localhost 4000
Trying ::1...
Trying 127.0.0.1...
Connected to localhost.
Escape character is '^]'.
Hello user!
Connection closed by foreign host.

Note that this server accepts only one connection (I will post new code for a multi-client server soon)!

Categories: C Tags: , ,

Semaphores in C

November 13, 2009 Leave a comment

Quando si parla di processi in C, è utile introdurre il concetto di SEMAPHORES. Un semaphore in informatica viene utilizzato pe gestire l’accesso ad una risorsa condivisa e viene implementato sulla base di un valore intero e di due primitive: UP e DOWN.

Posto che il valore dell’intero venga identificato con V, allora:

  • se V > 0 un’operazione di DOWN ne abbassa il valore di un’unità
  • se V = 0 un’operazione di DOWN viene messa in attesa finchè V > 0 e poi ne abbassa il valore di un’unità
  • un’operazione di UP aumenta il valore di V di un’unità

Un semaforo in C può essere creato utilizzando la seguente sintassi:

key_t key; //key
int sem_id; //semaphore id
int nsems = 10; //number of semaphores in the set

key = ftok("/home/belleforonte/aaa", 7);
sem_id = semget(key, nsems, 0666|IPC_CREAT); // last one is (access rights | create/re-use)

La variabile key rappresenta una chiave e viene creata tramite il comando ftok(const char *pathname, int proj_id); dove pathname indica il percorso di un file ESISTENTE (su una directory in cui dovete avere diritti di accesso) mentre proj_id è un intero che rappresenta un id.

Dopo aver creato la chiave, andiamo ad utilizzarla nella creazione/riutilizzo di un semaforo usando il comando

semget(key_t key, int nsems, int semflg); è da notare che un precedente semaforo avrebbe potuto essere reutilizzato tramite il comando semget(key, 0, 0) dove la key avrebbe dovuto essere stata la stessa utilizzata per la creazione.

La funzione vista in precedenza ritorna -1 se non può essere eseguita correttamente, dunque se il semaphore set non è stato creato.

Semaphore

Dopo aver creato il semaphore passiamo a capire come manipolarlo tramite le operazioni di UP e DOWN.

Per effettuare operazioni con un semaphore set facciamo qualcosa di simile:

int semop(int sem_id, struct sembuf *sops, unsigned nsops);

Dove: sem_id è il valore ottenuto dal comando semget, sops è un array di comandi sul semaphore identificati da una struttura del tipo:

struct sembuf {
  u_short sem_num; // indicates which one is the semaphore to control
  short sem_op; // indicate which one is the variable to increment/decrement
  short sem_flag;
}

mentre nsops è il numero di comandi nell’array.

Nella struttura sembuf: sem_num indica il numero del semaphore nel set, sem_op indica l’operazione da eseguire, mentre sem_flg
 dovrebbe essere posta a 0.

Il semaforo viene  comandato in base al valore di sem_op, infatti
:

  • se 
sem_op
 >
 0,
 il valore del semaphore viene aumenta di sem_op
  • se sem_op
<
0
  • o se il valore del semaphore è maggiore o uguale
 al valore assoluto di sem_op, il valore del semaphore è diminuito di sem_op e poi l’esecuzione del codice continua

    o se il valore del semaphore è minore del valore assoluto di sem_op,
 l’esecuzione è sospesa finchè un altro processo non incrementa il valore del semaphore

  • se sem_op = 0, l’esecuzione è sospesa finchè il semaforo è a valore 0

Prima di vedere un esempio concreto ricordo che è FONDAMENTALE eliminare i semaphores se non si usano più. Questa procedura viene eseguita nel seguente modo:

int semctl(sem_id, 0, IPC_RMID);

Inoltre i seguenti comandi da linea di comando possono essere utili, il primo per vedere i semafori attivi e il secondo per rimuoverne uno: sotto posto il codice per vederne il man:

bellerofonte@pegaso:~$ man ipcs
.....man di ipcs
bellerofonte@pegaso:~$ man ipcrm
.....man di ipcrm

Passiamo ora a vedere l’esempio del producer-consumer.

Cito Wikipedia:

“In informatica, il problema del produttore-consumatore (conosciuto anche con il nome di problema del buffer limitato o bounded-buffer problem) è un esempio classico di sincronizzazione tra processi. Il problema descrive due processi, uno produttore (in inglese producer) ed uno consumatore (consumer), che condividono un buffer comune, di dimensione fissata. Compito del produttore è generare dati e depositarli nel buffer in continuo. Contemporaneamente, il consumatore utilizzerà i dati prodotti, rimuovendoli di volta in volta dal buffer. Il problema è assicurare che il produttore non elabori nuovi dati se il buffer è pieno, e che il consumatore non cerchi dati se il buffer è vuoto.

La soluzione per il produttore è sospendere la propria esecuzione se il buffer è pieno; non appena il consumatore avrà prelevato un elemento dal buffer, esso “sveglierà” il produttore, che ricomincerà quindi a riempire il buffer. Allo stesso modo, il consumatore si sospenderà se il buffer è vuoto; non appena il produttore avrà scaricato dati nel buffer, risveglierà il consumatore. Questa soluzione può essere implementata tramite delle strategie di comunicazione tra processi, tipicamente con dei semafori. Una soluzione errata potrebbe dar luogo ad un deadlock, in cui entrambi i processi aspettano di essere risvegliati.”

Ecco una possibile implementazione del producer:

#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>

int main()
{
  key_t key = ftok("/home/belleforonte/Desktop/a", 1);//file a must exist(can be any file)
  int sem_id = semget(key, 1, 0666 | IPC_CREAT);
  getchar();

  struct sembuf sops = {
    .sem_num = 0,
    .sem_op = 1 // increment by one
  };

  semop(sem_id, &sops, 1);
}

E del  consumer:

#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>

int main()
{
  key_t key = ftok("/home/belleforonte/Desktop/a", 1);//file a must exist(can be any file)
  int sem_id = semget(key, 0, 0);

  struct sembuf sops = {
    .sem_num = 0,
    .sem_op = -1 // decrease by one
  };

  semop(sem_id, &sops, 1);
  printf("Key pressed\n");
}

Finito! 🙂

Categories: C Tags: ,

Pipes in C

November 12, 2009 Leave a comment

Il mio pc è ancora defunto..quindi anche oggi vi parlerò di C e non di Erlang 😦

Oggi tratterò le PIPES in C. Le pipes sono utilizzate per garantire un canale di comunicazione tra processi diversi e proprio come la traduzione di PIPE dall’inglese suggerisce, possono essere viste come un “tubo” con un foro di ingresso e uno di uscita.

Le PIPEs vengo gestite tramite due descrittori di file: uno viene usato per la scrittura e l’atro per la lettura di dati. Una PIPE viene dichiarata nel seguente modo:

int fd[2];
pipe(fd);

La prima riga introduce un array di 2 elementi che verranno usati come file descriptors, mentre la seconda va a creare una PIPE caratterizzata da tali descriptors.

Vediamo una un codice in cui dopo la creazione della PIPE verrà eseguito un fork: il processo figlio scriverà sulla PIPE, mentre il processo padre leggerà da essa.

#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>

int main()
{
  int fd[2], nbytes;
  pid_t pid;
  char string[] = "Hello world!\n";
  char readbuffer[80];

  pipe(fd);

  switch(pid = fork())
    {
    case -1:
      printf("Error in fork() command\n");
      close(fd[0]);
    case 0:
      close(fd[0]);
      write(fd[1], string, 13);
      _exit(0);
    default:
      close(fd[1]);
      nbytes = read(fd[0], readbuffer, sizeof(readbuffer));
      printf("Received string: %s", readbuffer);
    }
  return(0);
}

Il codice come potete vedere inizia con l’inclusione di alcune librerie che ci saranno utili, in seguito vengono dichiarate le variabili, alcune allo scopo di utilizzare la PIPE, altre per la lettura e la scrittura di stringhe sulla PIPE stessa.

Dopo aver creato la PIPE eseguiamo il fork e ne switchiamo il risultato: in caso di processo padre andiamo a leggere il valore presente in fd[0] (SEMPRE E SOLO UTILIZZATO PER LA LETTURA), assegniamo tale valore alla variabile readbuffer e ne stampiamo il valore a video.

In caso di processo figlio invece scriviamo il valore della variabile string in fd[1] che viene SEMPRE USATO PER LA SCRITTURA e poi terminiamo il processo.

Categories: C Tags: , ,

Processes in C

November 10, 2009 Leave a comment

Carissimi amici eccomi qua! Il mio portatile è defunto, ciao caro iBook..ti ho voluto bene!

Questo mi ha creato dei grossi problemi…infatti ho perso tutte le cosette che avevo salvato in quella deliziosa macchina…spero comunque di recuperare i dati in qualche modo!

Questo incoveniente mi permette tuttavia di scrivere un post sui processi in C, argomento totalmente nuovo per me, che mi sono stati introdotti nel corso di Distributed Systems in università.

Il primo esercizio da fare era il seguente:

Tramite i comandi messi a disposizione da C scrivere un programma che:

  • crei un nuovo thread
  • il nuovo thread (figlio) dorme per 4 secondi
  • il processo genitore aspetta finchè il figlio non finisce la propria esecuzione e mostra un messaggio

Il codice da me scritto è il seguente: (la spiegazione come al solito viene dopo!) 🙂

//es1.c
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>

int main() {
  pid_t pid; //type could have been int

  pid = fork();

  if (pid > 0) {
    sleep(4);
    exit(0);
  } else if (pid == 0) {
    wait();
    printf("Child terminated\n");
  } else {
    printf("Error in fork command\n");
    exit(1);
  }
}

Tale codice va compilato ed eseguito usando tali comandi:

bellerofonte@pegaso:~/Desktop$ gcc -o es1 es1.c
bellerofonte@pegaso:~/Desktop$ ./es1
Child has died

Il codice inizia includendo le 3 librerie che ci serviranno nel programma, esse vengono importate usando il comando #include.

Passiamo alla dichiarazione del main: si comincia dichiarando una variabile di tipo pid_t (che avrebbe potuto essere di tipo int), tale variabile servirà per rappresentare il Process Identifier dei vari processi, ovvero un numero personale che viene associato ad ogni processo e che lo identifica all’interno del sistema. A tale variabile viene in seguito assegnato il valore ottenuto dall’esecuzione del comando fork().

L’esecuzione di fork() può restituire diversi valori che vengono comunque inclusi in uno dei seguenti due casi:

  • 0 se il processo è un processo padre
  • un numero maggiore di 0 se il processo è un processo figlio

A simple fork

A questo punto come potete vedere usando un if-else o uno switch possiamo fare eseguire diversi codici a seconda che il processo risultante dal fork sia di tipo padre o figlio. Nel nostro esempio se il processo è di tipo figlio, esso “dorme” per 4 secondi utilizzando il comando sleep(Seconds) e poi termina la propria esecuzione; se il processo è invece di tipo padre usiamo il comando wait() che mette in pausa l’esecuzione finchè un processo figlio non termina la propria esecuzione. A tale istruzione potrebbe essere passato un PID come parametro….in tal caso il processo padre aspetta finchè non riceve un segnale dal processo identificato da tale PID che segnala la propria fine; Inoltre esso potrebbe ritornare un 127 se il PID passato non è associato a un processo esistente o 0 se non vi sono processi attivi oltre al padre.

Prossima volta (visto che non ho erlang sui PC della facoltà) vi mostrerò cosa sono le PIPE e i SEMAFORI in C.

Categories: C