Creating a process pool C Linux - c

I have an assignment and I am not quite sure how to go about it. Basically I have to create a coordinator process which creates 5 working processes which are waiting to be awakened. The coordinator passes a marker(integer) to the first process, then that process increments the marker by 1 and passes it to the next process. The coordinator process awakens the next process which does the same and so on. The so called marker should go through all the processes 10 times and in the end its value should be printed by the coordinator. Signals should be used as well as shared memory for the marker.
So I created 5 processes and I am thinking that on every iteration there should be a signal and a handler should be passed which will basically do all the work with the marker.
This is my first time working with processes. This is what I have so far:
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <sys/shm.h>
#include <signal.h>
#define numOfProcesses 5
pid_t procIDs[5];
void handler(int signum){
//marker and all work here
}
void createProcesses(){
int i;
for(i = 0; i < numOfProcesses; i++){
procIDs[i] = fork();
if(procIDs[i] < 0 ){
perror("Fork error!");
}else if(procIDs == 0){
pause();
}
}
}
int main(){
createProcesses();
int i;
for(i = 0; i < numOfProcesses; i++){
pkill(SIGUSR1, handler);
}
return 0;
}
I honestly don't know how to go about this. I would really appreciate a piece of advice. Thanks in advance!

This is what I have come up with. Sorry for answering, I couldn't find out how to format code in a comment. Anyway:
It should be 10 times each process. I am using shared memory so I guess I don't need a global variable for the marker? This is what I have come up with:
#include<stdio.h>
#include<unistd.h>
#include<stdlib.h>
#include<sys/types.h>
#include<sys/wait.h>
#include<sys/shm.h>
#include<signal.h>
#include<sys/ipc.h>
#define numOfProcesses 5
#define numOfLoops 10
pid_t* procIDs[5];
void createProcesses(){
int i;
for(i = 0; i < numOfProcesses; i++){
procIDs[i] = fork();
if(procIDs[i] < 0 ){
perror("Fork error!");
}
else if(procIDs == 0){
pause();
}
}
}
void init(){//init marker = 0
key_t mykey = ftok(".", 0);
int shID = shmget(mykey, sizeof(int), 0666 | IPC_CREAT);
int *data;
data = (int*) shmat(shID, 0, 0);
*data = 0;
}
int* getValue(){//get value of marker
key_t mykey = ftok(".", 0);
int shID = shmget(mykey, sizeof(int), 0666 | IPC_CREAT);
int *data = shmat(shID, 0, 0);
return data;
}
void increment(int sig){//increment + 1
if(sig == SIGUSR1){
int temp;
int* data;
data = getValue();
temp = *data;
temp++;
*data = temp;
}
}
void yourFunc(int count, pid_t* mypid, int mysig){
if(count == 0){
return;
}else{
printf("Signal sent :: to PID : %d\n", mypid);
kill(*mypid, SIGUSR1);
yourFunc(count -1, ++mypid, SIGUSR1);
}
}
int main(){
signal(SIGUSR1, increment);
init();
int i,j;
createProcesses();
for(j = 0; j < numOfLoops; j++){//loop every pid 10 times
pid_t* currProcess = procIDs[0];
yourFunc(numOfProcesses, currProcess, SIGUSR1);
}
int* data = getValue();
printf("Marker: %d\n", *data);
return 0;
}

I tried your problem, but I am really baffled by the structure of your question, its really unclear what your problem statement is.
10 times each(10 times per process or a total of 10 times(2 times per process)
You say the processes are waiting to be awakened, which hints that they are not child processes rather other processes running on the system, and would require a fifo to communicate.
Nevertheless, the following is what I could conclude from the limited information.
You need to create a function which would be invoked 10 times(loop) by the coordinator on the first process(waiting to be awakened)
The function would recursively invoke the second process and so on till the last sleeping process.
You'll have to use SIGUSR1, and define action for it in a custom signal handler,
eg.
signal(SIGUSR1,custom_handler)
You will need to keep marker as a global variable.
Because C is a procedural language and kernel's scheduling is not in your hands once a process terminates you cannot recall it or ensure same PID for a process on forking.
So if you are thinking of creating processes inside functions which will be paused and on getting a signal shall resume, fine.....!, But it would be a one-off.
That's all I can say by the limited information your question presents.
Following is the above idea in C.
Initialise count = 5 (no. of processes)in the caller.
mypid points to the first process's PID.
void party_time(int count, pid_t* mypid, int mysig)
{
if(count == 0)
return;
else
{
printf("Signal sent :: to PID : %d\n",*mypid);
kill(*mypid,SIGUSR1);
party_time(count - 1 ,++mypid,SIGUSR1);
}
}

Related

System call how to make parent wait for child

This is my code system call in C.
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
int n;
int i;
pid_t pid;
int time = 1000;
int sum = 0;
int main(void) {
printf("n: ");
scanf("%d", &n);
pid = fork();
if (pid < 0) {
printf("Fork Failed");
exit(-1);
} else if (pid == 0) {
//child
for (i = 1; i <= n; i++) {
sum += i;
}
printf("Sum of 1 to %d: %d\n", n, sum); // this is ok
} else {
// parent
wait(&time);
printf("Sum of 1 to %d: %d\n", n, sum); // this always return 0;
}
return 0;
}
I don't know why in parent's code block, the sum is always equal to 0.
How to make parent wait for child or am I doing something wrong ?
Waiting for the child works. However, your expectations are wrong.
Apparently you think that computations in the child process after the fork are visible in the parent process. They are not. The child is a new copy of the parent program at the time of fork. At that time, the parent's sum is 0 and stays that way.
There are several mechanisms to pass data from child to parent (the search term is interprocess communication, IPC).
exit() status
files
shared memory
pipes
signals
message queues
anything else I have missed
The issue here is the variable sum is not shared by the parent & child process, after fork() call the child will have its own copy of the variable sum.
Use shmget(),shmat() from POSIX api. Or use pthread which will share the same memory space for the newly created thread.
Update---
Added the shared memory to your code hopes this helps.
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <sys/shm.h>
int n;
int i;
pid_t pid;
int time = 1000;
int main(void) {
int shmid;
int *sum;
printf("n: ");
scanf("%d", &n);
/*request the shared memory from the OS using the shmget()*/
shmid = shmget(IPC_PRIVATE, sizeof(int), 0777|IPC_CREAT);
pid = fork();
if (pid < 0) {
printf("Fork Failed");
exit(-1);
} else if (pid == 0) {
//child
/* shmat() returns a char pointer which is typecast here
to int and the address is stored in the int pointer. */
sum = (int *) shmat(shmid, 0, 0);
for (i = 1; i <= n; i++) {
*sum += i;
}
printf("Sum of 1 to %d: %d\n", n, *sum); // this is ok
/* each process should "detach" itself from the
shared memory after it is used */
shmdt(sum);
} else {
// parent
wait(&time);
sum = (int *) shmat(shmid, 0, 0);
printf("Sum of 1 to %d: %d\n", n, *sum); // this always return 0;
shmdt(sum);
/*delete the cretaed shared memory*/
shmctl(shmid, IPC_RMID, 0);
}
return 0;
}
Refer for more info- https://man7.org/linux/man-pages/man2/shmget.2.html

sending synchronized signal among 3 processes

I'm working on signal project get a file with 0, increase to the input (ex ./count 300 sample.txt) by using sync signals p1->p2->p3->p1, after they increase number by 1, the fall in to sleep and call next one.
but I got stuck with two problems
how to and where to implement increasing number process, in signal handling or main( 0 -> 1 -> 2 ... input )
don't know how to implement with sigwait() or sigprocmask() what's the difference? . can i choose either one to guarantee that they are synchronized? or should I just use sleep?
belows are code that I've been working on so far.
#include <stdio.h>
#include <signal.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>
void sig_usr1(int signo)
{
char a;
sigset_t sigset, oldset;
sigemptyset (&oldset);
sigemptyset (&sigset);
sigaddset(&sigset, SIGUSR1);
sigprocmask(SIG_BLOCK, &sigset, &oldset);
fd =open("./sample.txt",O_RDWR);
pread (fd, a, sizeof(a));
if (argv[0]>a)
{a ++;
truncate ("./sample.txt", 0);
write(fd, a, sizeof(a));
}
}
int main (int argc, char** argv)
{
int fd;
int num=0;
struct sigaction usrsig ;
if(!(argv[0]>0))
printf("insert positive integer");
fd = open("./sample.txt",O_RDWR|O_CREAT|O_TRUNC);
write(fd, num ,sizeof(num);
pid_t child[3];
usrsig.sa_handler =sig_usr; // Parent
sigemptyset(&usrsig.sa_mask);
usrsig1.sa_flags = 0;
sigaction(SIGUSR1,&usrsig, 0);
for ( i=0; i<3; i++)
{
child[i] = fork();
if(child[i] == 0)
break;
}
pid_t prev;
if(i ==0) prev = getppid();
else prev = child[i-1];
kill(pid_prev, SIGUSR1)
}

Synchronization of parent and child with SIGUSR signal in C. Make parent and child read one after the other

I have created a two way communication between parent and child processes using two pipes. Parent and child write data and I was able to make them read the data from each other. Parent writes numbers 1 to 5, and child writes numbers from 6 to 10. But I want parent to start reading data the first, and then reading continues in this order switching from parent to child until all the data are read: 6,1,7,2,8,3,9,4,10,5. I have tried to synchronize the reading with SIGUSR1 but when the parent is reading for the second time the program stops. I have searched a lot to find where the problem can be, and tried some tips and alike working examples, but nothing seems to help. Here is my code:
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <errno.h>
#include <signal.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <sys/stat.h>
void paction(int dummy)
{
printf("P*************************************************\n");
}
void caction(int dummy)
{
printf("C*************************************************\n");
}
int main()
{
int pfd[2];
int pfd2[2];
pid_t cfork, pfork;
if (pipe(pfd) == -1 || pipe(pfd2) == -1) {
fprintf(stderr,"Pipe failed");
exit(1);
}
cfork = fork();
signal(SIGUSR1, paction);
if (cfork == -1) {
printf("Fork Failed\n");
exit(1);
}
else if (cfork > 0) { /*parent process*/
int numbers[] = {1, 2,3, 4, 5};
int numbers2[] = { 6, 7,8, 9, 10 };
close(pfd[0]); /*close read end, write and then close write end*/
/*write part*/
int limit = 5;
int i;
for (i = 0; i < limit; i++) {
printf("Parent sends: %d\n", numbers[i]);
write(pfd[1], &numbers[i], sizeof(numbers[i]));
printf("Child sends: %d\n", numbers2[i]);
write(pfd2[1], &numbers2[i], sizeof(numbers2[i]));
}
printf("***************************************************\n");
close(pfd[1]);
close(pfd2[1]);
/*read part/////////////////////////////////////////*/
int temp;
int reads = 5;
int j;
for (j = 0; j < reads; j++) {
sleep(1);
read(pfd2[0], &temp, sizeof(temp));
printf("Parent gets: %d\n", temp);
kill(cfork, SIGUSR1);
pause();
}
/*printf("***************************************************\n");*/
kill( cfork, SIGUSR1 );
close(pfd2[0]);
}
else { /*child process*/
signal(SIGUSR1, caction);
close(pfd[1]);
int temp;
int reads = 5;
int j;
pfork = getppid();
for (j = 0; j < reads; j++) {
sleep(1);
read(pfd[0], &temp, sizeof(temp));
printf("Child gets: %d\n", temp);
kill(getppid(), SIGUSR1);
pause();
}
/*printf("***************************************************\n");*/
close(pfd[0]);
close(pfd2[0]);
}
return 0;
}
My output looks like this:
> Parent sends:1
> Child sends:6
> Parent sends:2
> Child sends:7
> Parent sends:3
> Child sends:8
> Parent sends:4
> Child sends:9
> Parent sends:5
> Child sends:10
> **************************************************************
Parent gets:6
> C************************************************************
> Child gets:1
> P*************************************************************
> Parent gets:7
And here is when it stops.
If someone can help me I would really appreciate it because I really want to know where the problem is, and since I am a beginner in C programming and processes!
Thank you in advance
printf() is not an async-safe function. Calling printf() in both normal code and a signal handler will cause undefined behavior. In particular, printf() may need to take a lock on the output-stream, while taking locks in signal-handlers is very inadvisable (risk of self-deadlock).
Maybe it is a bad idea to use signals, but I had a task in which it was assigned to use SIGUSR1. I solved the issue by adding:
static struct sigaction pact, cact;
/* set SIGUSR1 action for parent */;
pact.sa_handler = p_action;
sigaction(SIGUSR1, &pact, NULL);
After the parent was assigned the first action, it worked fine.
Thank you:)

API for obtaining and Releasing a PID

I have my first Hw assignment from the book. Can anyone help out in designing my code. I don't know where to start. i'm thinking of using an array with all zeros as the first step but i really don't know what to do. I don't understand how creating a parent and when i do that it should initialize a shared memory segment is this where my array should come in? The book is pretty good but really lacking in explaining exactly what i need to do in my program or and doesnt provide any sample output. Thanks for any help
An operating system’s pid manager is responsible for managing process
identifiers. When a process is first created, it is assigned a unique
pid by the pid manager. The pid is returned to the pid manager when
the process completes execution, and the manager may later reassign
this pid. Process identifiers are discussed more fully in Section
3.3.1. What is most important here is to recognize that process identifiers must be unique; no two active processes can have the same
pid. Use the following constants to identify the range of possible pid
values:
#define MIN PID 300 #define MAX PID 5000 You may use any data structure of your choice to represent the availability of process
identifiers. One strategy is to adopt what Linux has done and use a
bitmap in which a value of 0 at position i indicates that a process id
of value i is available and a value of 1 indicates that the process id
is currently in use.
int allocate map(void)—Create sand initializes a data structure for
representing pids; returns—1 if unsuccessful, 1 if successful
int allocate pid(void) — Allocates and returns a pid; returns — 1
if unable to allocate a pid (all pids are in use)
void release pid(int pid)—Releases a pid
Perhaps this is late. But I too came across the same question, worked out the following and wanted to cross-check it. I couldn't find any complete solution but anyway, here is what I did:
#include <stdio.h>
#include <stdlib.h>
#include <limits.h>
#define MIN_PID 500
#define MAX_PID 3000
#define cb CHAR_BIT
int sz = MAX_PID - MIN_PID + 1;
unsigned char *b;
int allocate_map();
int allocate_pid();
void release_pid(int pid);
int main()
{
int map = allocate_map();
if (map == 1) {
printf("\nData Structure initialized.\n");
int id = 0, i = 0;
while (i < 15) {
int val = allocate_pid();
printf("\nProcess %d: pid = %d", i+1, val);
i++;
}
release_pid(503); printf("\nProcess 503 released.");
release_pid(505); printf("\nProcess 505 released.");
int val = allocate_pid(); printf("\nProcess %d : pid = %d\n", i+1, val);
}
else printf("\nFailed to initialize data structure.\n");
}
/* Creates and initializes a data structure for representing pids;
returns —1 if unsuccessful, 1 if successful */
int allocate_map() {
b = (unsigned char*)malloc((sz+cb-1)/cb * sizeof(char));
if (b) return 1;
return -1;
}
/* Allocates and returns a pid; returns -1
if unable to allocate a pid (all pids are in use) */
int allocate_pid() {
int i = 0;
int pid = b[i/cb] & (1 << (i & (cb-1)));
while (pid != 0) {
i++;
pid = b[i/cb] & (1 << (i & (cb-1)));
}
if (i+MIN_PID > MAX_PID) return -1;
b[i/cb] |= 1 << (i & (cb-1));
return i+MIN_PID;
}
/* Releases a pid */
void release_pid(int pid) {
if (pid < 500) {
printf("\nInvalid PID: It should lie between 500 and 3000.");
return;
}
int i = pid - MIN_PID;
b[i/cb] &= ~(1 << (i & (cb-1)));
}
I also encounter this question while reading the Operating system concepts, 10th edition book. I didn't find any reference to cross-check my solution.
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#include <stdbool.h>
#define MIN_PID 300
#define MAX_PID 5000
int get_random();
int allocate_map(void);
int allocate_pid();
void release_pid();
bool* pid_map;
int main() {
// initiate pid map
if(allocate_map() == -1){
printf("unable to create the pid map\n");
}
// sample pid for feature release
int pid1, pid2;
// allocate pids
for(int i = 0; i < 1000; i ++){
int pid = allocate_pid();
if(i == 3) pid1 = pid;
if(i == 4) pid2 = pid;
printf("PID: %d\n", pid);
}
// release pids
release_pid(pid1);
release_pid(1000);
release_pid(pid2);
}
int allocate_map(void){
srand(time(0));
pid_map = malloc(sizeof(bool) * MAX_PID); // yah, allocated extra 300 pid
return pid_map == NULL ? -1 : 1;
}
int allocate_pid(){
int pid = get_random();
while(pid_map[pid] == true){
pid = get_random();
}
pid_map[pid] = true;
return pid;
}
void release_pid(int pid){
if(pid_map[pid] == true){
pid_map[pid] = false;
printf("Release pid %d\n", pid);
} else {
printf("PID %d is not associated with any process\n", pid);
}
}
//to get a random number between max and min pid
int get_random(){
return (rand() % (MAX_PID - MIN_PID + 1) + MIN_PID);
}
Create a parent which initializes a shared memory segment, and then creates a number of children. Each child scans the memory segment looking for a free slot (meaning a memory location offset from the base address), and returns the index of that slot as its simulated “pid”, replacing the 0 value with its child ID or some other indication that the slot is taken. This is a start

Calculating expression with multiple fork() children?

Suppose we have expression g=(a+b)*(c+d)-(e/f) with hard-coded arbitrary numbers for variables. I would like to calculate this expression using multiple child processes in order to better understand how fork() works.
My first attempt was to calculate (a + b) on child pid1, (c + d) on child pid2, (e / f) on child pid3, and then do summation & subtraction in the parent process.
Well, to my disappointment, (a + b) calculation done in the child process pid1 did not affect double expression1 variable in the parent process. I think the reason behind that - each fork() creates a separate memory space; as soon as a child process exits, all calculations done in that child process are gone.
What do you usually do in a situation like this? I thought maybe I could nest fork() child process within a child process to calculate (a + b) first; wait; then (c + d); wait; (e / f); wait; the first child calculates the entire expression; child return(0); parent terminates.
But I think there's an easier solution to this problem, am I right?
If you insist on using fork() , so here is my answer now using child process and shared memory
Note that exit() is used here the way it is expected by the system: to signalize if the child has exited normally or not.
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/shm.h>
struct shared{
int a_b;
int c_d;
int e_f;
};
const int a=1,b=2,c=3,d=4,e=6,f=2;
const key_t key = 1234;
pid_t pab,pcd,pef;
void* shared_mem;
int main(){
//Parent process create the shared memory
int shmid = shmget(key,sizeof(struct shared), 0666|IPC_CREAT);
if(shmid == -1) exit(EXIT_FAILURE);
//Fork child
pab = fork();
if(pab == 0){
//Inside process ab
//attach to shared memory
shared_mem = shmat(shmid,(void*) 0,0);
if(shared_mem == (void*) -1) exit (EXIT_FAILURE);
struct shared* shared_data = (struct shared*) shared_mem;
shared_data->a_b = a +b;
//detach
if(shmdt(shared_mem) == -1) exit (EXIT_FAILURE);
exit(EXIT_SUCCESS);
}else {
pcd = fork();
if(pcd == 0){
//Inside process cd
//attach to shared memory
shared_mem = shmat(shmid,(void*) 0,0);
if(shared_mem == (void*) -1) exit (EXIT_FAILURE);
struct shared* shared_data = (struct shared*) shared_mem;
shared_data->c_d = c+d;
//detach
if(shmdt(shared_mem) == -1) exit (EXIT_FAILURE);
exit(EXIT_SUCCESS);
}else{
pef = fork();
if(pef == 0){
//Inside process ef
//attach to shared memory
shared_mem = shmat(shmid,(void*) 0,0);
if(shared_mem == (void*) -1) exit (EXIT_FAILURE);
struct shared* shared_data = (struct shared*) shared_mem;
shared_data->e_f = e/f;
//detach
if(shmdt(shared_mem) == -1) exit (EXIT_FAILURE);
exit(EXIT_SUCCESS);
}
}
}
//Wait child process termination
int status_ab,status_cd,status_ef;
waitpid(pab,&status_ab,0);
waitpid(pcd,&status_cd,0);
waitpid(pef,&status_ef,0);
//Check if all child exited normally
if(!WIFEXITED(status_ab) || !WIFEXITED(status_cd)||!WIFEXITED(status_ef)){
exit(EXIT_FAILURE);
}
//Parent attaches to memory
shared_mem = shmat(shmid,(void*) 0,0);
if(shared_mem == (void*) -1) exit (EXIT_FAILURE);
struct shared* shared_data = (struct shared*) shared_mem;
//Calculate result
int result = (shared_data->a_b)*(shared_data->c_d)-(shared_data->e_f);
printf("Result is %d\n", result);
//Parent detaches from shared memory and deletes
if(shmdt(shared_mem) == -1) exit (EXIT_FAILURE);
if(shmctl(shmid,IPC_RMID,0) == -1) exit(EXIT_FAILURE);
return EXIT_SUCCESS;
}
fork()ing the processes, then waitpid()ing on their return values:
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
int main()
{
//whatever values you like:
int a = 1;
int b = 2;
int c = 3;
int d = 4;
int e = 15;
int f = 6;
int a_plus_b_pid;
int c_plus_d_pid;
int e_div_f_pid;
int a_plus_b;
int c_plus_d;
int e_div_f;
a_plus_b_pid = fork();
if(a_plus_b_pid)
{
c_plus_d_pid = fork();
if(c_plus_d_pid)
{
e_div_f_pid = fork();
if (e_div_f_pid)
{
//wait for our processes to exit, with our results, and stash the computed values.
waitpid(a_plus_b_pid, &a_plus_b, 0);
waitpid(c_plus_d_pid, &c_plus_d, 0);
waitpid(e_div_f_pid, &e_div_f, 0);
//The 8 least-significant bits carry info that we're not interested in here, so shift them out:
a_plus_b >>= 8;
c_plus_d >>= 8;
e_div_f >>= 8;
printf("%d %d %d %d\n", a_plus_b, c_plus_d, e_div_f, a_plus_b * c_plus_d - e_div_f);
}
else
{
exit (e/f);
}
}
else
{
exit (c+d);
}
}
else
{
exit (a+b);
}
}
This is a version using pthreads:
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <pthread.h>
volatile int a_b;
volatile int c_d;
volatile int e_f;
const int a=1,b=2,c=3,d=4,e=6,f=2;
void* calc_ab(void*);
void* calc_cd(void*);
void* calc_ef(void*);
int main(){
pthread_t ab_thread, cd_thread, ef_thread;
pthread_create(&ab_thread,NULL,calc_ab,NULL);
pthread_create(&cd_thread,NULL,calc_cd,NULL);
pthread_create(&ef_thread,NULL,calc_ef,NULL);
pthread_join(ab_thread, NULL);
pthread_join(cd_thread, NULL);
pthread_join(ef_thread,NULL);
int result = a_b*c_d-e_f;
printf("Result is %d\n", result);
return EXIT_SUCCESS;
}
void* calc_ab(void* arg){ a_b = a+b;pthread_exit(NULL);}
void* calc_cd(void* arg){ c_d = c+d;pthread_exit(NULL);}
void* calc_ef(void* arg){ e_f = e/f;pthread_exit(NULL);}
To compile you have to link against pthread:
gcc pthread.c -lpthread -o teste
Notes
Note that variables that are shared between the main thread and a child thread are declared volatile. This prevent the compiler of doing some memory optimizations that could prevent a write done in one thread not to be seen by others.
Each child thread writes to a different shared variable. I wanted to keep the code simple, not having to handle synchronization explicitly.
The main thread only reads the shared variable only after it has returned from a pthread_join for the thread that changed it. Again I wanted to keep the code simple, not having to handle synchronization explicitly.
First, you don't need processes at all to do arbitrary computation. Emabedding an interpreter like e.g. lua might be simpler.
Of course, each process has its own address space. Type cat /proc/self/maps to get information about the process running that cat command.
If you insist on using processes to learn how they can communicate thru pipes, you might use something like popen(3) which will use some syscalls to start and pipe a command.
char cmd[80];
int a, b, sum;
/// fill a & b
snprintf (cmd, sizeof(cmd), "echo $[%d + %d]", a, b);
FILE* pcmd = popen(cmd, "r");
if (fscanf (pcmd, "%d", &sum)>0) {
// do something with sum
};
pclose(pcmd);
And you should read a good book like Advanced Unix Programming and Advanced Linux Programming. The real thing is to understand syscalls like fork(2), waitpid(2), execve(2), pipe(2), dup(2), etc.... To understand what syscalls(2) are done by some command or program, use strace

Resources