2

I have an integer set to 0, I want to increment it by 10000 using the first process and by 5000 using each of 2 other processes I create.

Current code:

#include<stdio.h>
#include<stdlib.h>
#include<sys/mman.h>
#include<unistd.h>

int main() {
  int* shared_int = (int*) mmap(NULL, sizeof(int), PROT_READ | PROT_WRITE,
      MAP_SHARED | MAP_ANONYMOUS, -1, 0);
  *shared_int = 0;
  if (shared_int == MAP_FAILED) {
    perror("mmap failure");
    exit(EXIT_FAILURE);
  }

  pid_t pid = fork();

  if (pid > 0) {
    *shared_int = *shared_int + 10000;
    printf("Value after P1 incremention: ");
    printf("%d", *shared_int);
    exit(0);
  }

  pid_t pid2 = fork();

  if (pid == 0) {
    *shared_int = *shared_int + 5000;
    printf("Value after P2 incremention: ");
    printf("%d", *shared_int);
    exit(0);
  }

  if (pid2 == 0) {
    *shared_int = *shared_int + 5000;
    printf("Value after P3 incremention: ");
    printf("%d", *shared_int);
  }
  return 1;
}

Intended output:

Value after P1 incremention: 10000 
Value after P2 incremention: 15000
Value after P3 incremention: 20000

Current Output:

Value after P1 incremention: 10000 
Value after P2 incremention: 15000
Value after P2 incremention: 15000

I tried entering a random printf statement at the start of my third if and it didn't execute so it seems like the third process is terminated before that and is entering the second if when it shouldn't.

chux - Reinstate Monica
  • 143,097
  • 13
  • 135
  • 256
Sergio
  • 275
  • 1
  • 15
  • 3
    there is no memory sharing here, each process has is own mapped region – OznOg Mar 14 '20 at 15:52
  • I changed this after I tried putting `pid_t pid=fork(); pid_t pid2; if(pid>0){ pid2=fork(); } int* shared_int=(int*) mmap(NULL, sizeof(int), PROT_READ | PROT_WRITE, MAP_SHARED | MAP_ANONYMOUS,-1, 0);` and I got an even worse output: Value after P1 incremention: 10000 Value after P2 incremention: 5000 Value after P1 incremention: 10000 – Sergio Mar 14 '20 at 16:00
  • see https://stackoverflow.com/questions/5656530/how-to-use-shared-memory-with-linux-in-c – OznOg Mar 14 '20 at 16:02
  • You should include a newline at the end of the format string for each `printf()` pair, and each pair should be a single `printf()` — there is no advantage to separating the main 'comment' string from the formatting for the number. However, that's mechanics. The claimed output doesn't match the output from the code because of the discrepancy in the newlines. – Jonathan Leffler Mar 14 '20 at 16:09
  • 1
    Does this answer your question? [How to use shared memory with Linux in C](https://stackoverflow.com/questions/5656530/how-to-use-shared-memory-with-linux-in-c) – ShadowRanger Mar 14 '20 at 16:12
  • @JonathanLeffler I applied this and the output didn't change, unless I misunderstood your suggestion then that's not it. – Sergio Mar 14 '20 at 16:12
  • My comments are about the cosmetics of the code and its output — not about the functionality of the shared memory, or lack of shared memory. So, the numbers shouldn't change as a result of the suggested edits. The cleanliness of the presentation should. – Jonathan Leffler Mar 14 '20 at 16:14
  • @JonathanLeffler Yes I adjusted the output here for better readability, still have to fix the other part. – Sergio Mar 14 '20 at 16:15
  • Doing the mapping after the forks means that each process has its own, wholly unshared segment of memory. The code in the question is correct in setting up the memory before doing any forking — which addresses your observations in your [comment](https://stackoverflow.com/questions/60684538/changing-the-value-of-an-integer-using-different-processes-in-c-whats-going-wr?noredirect=1#comment107366552_60684538) above. – Jonathan Leffler Mar 14 '20 at 16:17
  • @ShadowRanger I am sorry but this didn't make what I am doing wrong clearer for me, thank you for trying to help either way. – Sergio Mar 14 '20 at 16:19
  • Consider inserting `wait(NULL);` (ugh — that's the lazy way of doing it, but it works) between `if (pid == 0) { … }` and `if (pid2 == 0) { … }`. That forces P3 to wait for P2 to complete, I think. And should therefore avoid both processes reading the memory at the same time and setting it to the same value. Also, make one of the processes increment by 2500 or 7500 (or any value other than 10000 or 5000); it might make things easier to understand. Perhaps capture and print the 'before' value as well as the expected and actual after values. Multicore machines really do run things in parallel. – Jonathan Leffler Mar 14 '20 at 16:20
  • @JonathanLeffler output didn't change, as mentioned in my question it seems like `if (pid2 == 0) { … }` isn't executing in the first place. – Sergio Mar 14 '20 at 16:23
  • Well, yes — now you mention it again. Unless the `fork()` failed so `pid` is `-1`, when you reach the point of `pid2 = fork();`, `pid` is zero. So after `pid2 = fork();`, `pid` is still `0` in both parent and child processes. The first step in debugging code if you're not going to run a debugger is to print everything that might be relevant — especially PID values. Use `getpid()` to identify which process is printing the PID variables — `printf("PID %d: pid = %d, pid2 = %d\n", (int)getpid(), (int)pid, (int)pid2);` etc. _[…continued…]_ – Jonathan Leffler Mar 14 '20 at 16:26
  • alright, will see, thank you! – Sergio Mar 14 '20 at 16:30
  • 1
    –[…continuation…]_ The `(int)` casts are me covering my butt; there's no defined format specifier for `pid_t` (sadly; POSIX should extend `` to provide format specifiers for its types), and if it is `long long` (not impossible, though somewhat improbable), using `%d` would give problems. The casts ensure correct matching of format specifier and value, at the risk of chopping off critical information if the type actually is `long long` and a PID is more than 2^31-1. Also, the P1 process should probably wait for its child to complete before exiting. – Jonathan Leffler Mar 14 '20 at 16:31
  • Surely you mean `if (pid2 != 0)` not `if (pid == 0)` ? The lack of any semaphores troubles me, as well. – Chris Hall Mar 14 '20 at 16:38
  • An alternative to the casts would be `static_assert(sizeof(pid_t) == sizeof(int), "pid_t is not equivalent to int; fix the printf formats");` and then blithely ignore the problem. – Jonathan Leffler Mar 14 '20 at 16:38
  • @JonathanLeffler, I'm wondering about the suggestion to use: `static_assert` as that is a C++ item, not a C item. – user3629249 Mar 15 '20 at 19:41
  • It is part of C99, IIRC — certainly in C11. It's spelt `_Static_assert` by default, but including `` means it can be spelt `static_assert`. – Jonathan Leffler Mar 15 '20 at 19:43
  • OT: the function: `fork()` has three kinds of returned values: <0 means an error occurred ==0 means in the child process >0 means in the parent process. The code should be checking for all three conditions rather than assuming that the call to `fork()` worked successfully – user3629249 Mar 15 '20 at 19:44
  • @JonathanLeffler, Thanks for the 'heads up'. I guess my 40+ years with C is showing its' age spots. – user3629249 Mar 15 '20 at 19:46
  • @user3629249: I checked in the C11 standard's [Forward](http://port70.net/~nsz/c/c11/n1570.html#Forewordp6) and 'static assertions' are listed as new feature in C11 (so it was not in C99, but is in both the current C18 and previous C11 standards). – Jonathan Leffler Mar 15 '20 at 20:37

2 Answers2

3

Diagnosis

There are a variety of problems, most of them diagnosed in the comments.

One undiagnosed problem is that you set *shared_int = 0; before checking that the memory mapping succeeded. Don't!

I changed this after I tried putting:

pid_t pid = fork();
pid_t pid2;
if (pid > 0)
    pid2 = fork();
int* shared_int = (int*) mmap(NULL, sizeof(int), PROT_READ | PROT_WRITE, MAP_SHARED | MAP_ANONYMOUS, -1, 0);

and I got an even worse output:

Value after P1 incremention: 10000
Value after P2 incremention: 5000
Value after P1 incremention: 10000 

Doing the mapping after the forks means that each process has its own, wholly unshared segment of memory. The code in the question is correct in setting up the memory before doing any forking.

You should include a newline at the end of the format string for each printf() pair, and each pair should be a single printf() — there is no advantage to separating the main 'comment' string from the formatting for the number. However, that's mechanics. The claimed output doesn't match the output from the code because of the discrepancy in the newlines.

[Partially relevant comment] Consider inserting wait(NULL); (ugh — that's the lazy way of doing it, but it works) between if (pid == 0) { … } and if (pid2 == 0) { … }. That forces P3 to wait for P2 to complete, I think. And should, therefore, avoid both processes reading the memory at the same time and setting it to the same value. Also, make one of the processes increment by 2500 or 7500 (or any value other than 10000 or 5000); it might make things easier to understand. Perhaps capture and print the 'before' value as well as the expected and actual after values. Multicore machines really do run things in parallel.

Also, the P1 process should probably wait for its child to complete before exiting.

Output didn't change, as mentioned in my question it seems like if (pid2 == 0) { … } isn't executing in the first place.

Well, yes — now you mention it again — unless the fork() failed so pid is -1, when you reach the point of pid2 = fork();, pid is 0 (and the process is the child process of the one that was launched originally). So after pid2 = fork();, pid is still 0 in both the child and the grandchild processes, so both of them execute the if (pid == 0) { … } block of code.

The first step in debugging code if you're not going to run a debugger is to print everything that might be relevant — especially PID values. Use getpid() to identify which process is printing the PID variables

printf("PID %d: pid = %d, pid2 = %d\n", (int)getpid(), (int)pid, (int)pid2);

The (int) casts are me covering my butt; there's no defined format specifier for pid_t (sadly; POSIX should extend <inttypes.h> to provide format specifiers for its types), and if pid_t is a typedef for long long (not impossible, though somewhat improbable), using %d would give problems. The casts ensure correct matching of format specifier and value, at the risk of chopping off critical information if the type actually is long long and a PID is more than 231-1.

An alternative to the casts would be:

static_assert(sizeof(pid_t) == sizeof(int), "pid_t is not equivalent to int; fix the printf formats");

and then blithely ignore the problem.

Prescription

This code puts many of the ideas outlined in the diagnosis into effect:

#include <assert.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/mman.h>
#include <sys/wait.h>
#include <unistd.h>

static_assert(sizeof(pid_t) == sizeof(int), "pid_t is not equivalent to int; fix printf formats");

static void wait_for_kids(void);

int main(void)
{
    int *shared_int = (int *) mmap(NULL, sizeof(int), PROT_READ | PROT_WRITE,
                                   MAP_SHARED | MAP_ANONYMOUS, -1, 0);
    if (shared_int == MAP_FAILED)
    {
        perror("mmap failure");
        exit(EXIT_FAILURE);
    }
    *shared_int = 0;

    pid_t pid = fork();
    if (pid < 0)
    {
        perror("fork failure");
        exit(EXIT_FAILURE);
    }

    if (pid > 0)
    {
        *shared_int = *shared_int + 10000;
        printf("Value after P1 increment: %d\n", *shared_int);
        wait_for_kids();
        exit(0);
    }

    assert(pid == 0);

    pid_t pid2 = fork();
    if (pid2 < 0)
    {
        perror("fork failure");
        exit(EXIT_FAILURE);
    }

    if (pid2 > 0)
    {
        *shared_int = *shared_int + 5000;
        printf("Value after P2 increment: %d\n", *shared_int);
        wait_for_kids();
        exit(2);
    }

    assert(pid == 0 && pid2 == 0);
    {
        *shared_int = *shared_int + 2500;
        printf("Value after P3 increment: %d\n", *shared_int);
        wait_for_kids();    /* But no kids to wait for! */
        exit(3);
    }
    /*NOTREACHED*/
    return 32;
}

static void wait_for_kids(void)
{
    int status;
    int corpse;
    while ((corpse = wait(&status)) > 0)
        printf("%d: PID %d exited with status 0x%.4X\n", getpid(), corpse, status);
}

Sample output (tested on a MacBook Pro running macOS Mojave 10.14.6 with XCode 11.3.1 and using a home-compiled GCC 9.3.0):

Value after P1 increment: 10000
Value after P2 increment: 15000
Value after P3 increment: 17500
7439: PID 7440 exited with status 0x0300
7438: PID 7439 exited with status 0x0200

This is what you wanted, I believe (give or take the 'exited' messages).

Using functions

Here's a variant of the program above that encapsulates the main printing etc into a function. It uses some error reporting code that is available in my SOQ (Stack Overflow Questions) repository on GitHub as files stderr.c and stderr.h in the src/libsoq sub-directory.

#include "stderr.h"
#include <assert.h>
#include <stdio.h>
#include <stdlib.h>
#include <stdnoreturn.h>
#include <sys/mman.h>
#include <sys/wait.h>
#include <unistd.h>

static_assert(sizeof(pid_t) == sizeof(int), "pid_t is not equivalent to int; fix printf formats");

static void wait_for_kids(void);
static noreturn void increment_memory(int *mem, int pid, int inc, int status);

int main(int argc, char **argv)
{
    err_setarg0(argv[0]);
    if (argc != 1)
        err_usage("");
    err_setlogopts(ERR_PID);

    int *shared_int = (int *) mmap(NULL, sizeof(int), PROT_READ | PROT_WRITE,
                                   MAP_SHARED | MAP_ANONYMOUS, -1, 0);
    if (shared_int == MAP_FAILED)
        err_syserr("mmap failed: ");
    *shared_int = 0;

    pid_t pid = fork();
    if (pid < 0)
        err_syserr("fork failed: ");

    if (pid > 0)
        increment_memory(shared_int, 1, 10000, 0);

    assert(pid == 0);

    pid_t pid2 = fork();
    if (pid2 < 0)
        err_syserr("fork failed: ");

    if (pid2 > 0)
        increment_memory(shared_int, 2, 5000, 2);

    assert(pid == 0 && pid2 == 0);
    increment_memory(shared_int, 3, 2500, 2);

    /*NOTREACHED*/
    return 32;
}

static void wait_for_kids(void)
{
    int status;
    int corpse;
    while ((corpse = wait(&status)) > 0)
        printf("%d: PID %d exited with status 0x%.4X\n", getpid(), corpse, status);
}

static void increment_memory(int *mem, int num, int inc, int status)
{
    int old = *mem;
    *mem += inc;
    printf("%d: [%5d, %5d, %5d] Value after P%d increment: %d\n", getpid(), old, inc, old + inc, num, *mem);
    wait_for_kids();
    exit(status);
}

This has more detailed information in the printing. Example output:

13403: [    0, 10000, 10000] Value after P1 increment: 10000
13404: [10000,  5000, 15000] Value after P2 increment: 15000
13405: [15000,  2500, 17500] Value after P3 increment: 17500
13404: PID 13405 exited with status 0x0200
13403: PID 13404 exited with status 0x0200

I also created a variant of this with the call to wait_for_kids() before the printf() statement instead of after it. It generates, for example:

13408: [15000,  2500, 17500] Value after P3 increment: 17500
13407: PID 13408 exited with status 0x0200
13407: [10000,  5000, 15000] Value after P2 increment: 17500
13406: PID 13407 exited with status 0x0200
13406: [    0, 10000, 10000] Value after P1 increment: 17500

You'd get still different results if you placed the wait_for_kids() call before modifying the shared memory.

13411: [    0,  2500,  2500] Value after P3 increment: 2500
13410: PID 13411 exited with status 0x0200
13410: [ 2500,  5000,  7500] Value after P2 increment: 7500
13409: PID 13410 exited with status 0x0200
13409: [ 7500, 10000, 17500] Value after P1 increment: 17500

Beware of piping the output from such programs — it changes the output from line buffered to fully buffered.

Jonathan Leffler
  • 730,956
  • 141
  • 904
  • 1,278
  • 1
    As `pid_t` is signed, code could form a wide type and print that: `printf("PID %jd\n", (intmax_t) getpid());` without risk of losing information. – chux - Reinstate Monica Mar 14 '20 at 17:10
  • Yes: that is an option — but not precisely a pleasant one (not that the casting to `(int)` is any better). On the whole, I think the static assertion is best — it covers the improbable case without cluttering the code afterwards. – Jonathan Leffler Mar 14 '20 at 17:11
  • I suppose using `static inline intmax_t getpid_max(void) { return getpid(); }` and then calling `getpid_max()` and using `%jd` formats would avoid the repeated casts with minimal overhead. Using `intmax_t` elsewhere (`intmax_t pid = fork();`) etc would also work; with strict prototypes for all functions, there'd only be a problem if the value (or a pointer to a variable) was used in the variable part of a variable argument list (so mainly printing or scanning, and you seldom scan for PID values). – Jonathan Leffler Mar 14 '20 at 17:58
0

the following proposed code:

  1. cleanly compiles
  2. properly checks for errors
  3. performs the desired functionality
  4. uses '\n' in the calls to printf() format strings so data is immediately output to the terminal
  5. parent process properly waits for child process to exit
  6. corrects the coding/logic problems as exposed in the comments to the question
  7. contains appropriate horizontal/vertical spacing for readability

Note: this kind of statement:

*shared_int = *shared_int + 10000;

can be shortened to:

*shared_int += 10000;

And now, the proposed code:

#include <stdio.h>
#include <stdlib.h>
#include <sys/mman.h>
#include <unistd.h>

#include <sys/types.h>
#include <sys/wait.h>

int main( void ) 
{
    int* shared_int = (int*) mmap( NULL, sizeof(int), 
        PROT_READ | PROT_WRITE, MAP_SHARED | MAP_ANONYMOUS, -1, 0 );

    if ( shared_int == MAP_FAILED )
    {
        perror( "mmap failure" );
        exit( EXIT_FAILURE );
    }

    *shared_int = 0;

    pid_t pid = fork();

    switch( pid )
    {
        case -1:
            perror( "fork1 failed" );
            exit( EXIT_FAILURE );
            break;

        case 0:
            //child1
            *shared_int = *shared_int + 5000;
            printf( "Value after C1 incremention: " );
            printf( "%d\n", *shared_int );
            exit( EXIT_SUCCESS );
            break;

        default:
            // parent
            waitpid( pid, NULL, 0 );
            *shared_int = *shared_int + 10000;
            printf( "Value after P1 incremention: " );
            printf( "%d\n", *shared_int );
            break;
    }  // end switch

    // in parent process

    pid_t pid2 = fork();

    switch( pid2 )
    {
        case -1:
            perror( "fork2 failed" );
            exit( EXIT_FAILURE );
            break;

        case 0:
            // child2
            *shared_int = *shared_int + 5000;
            printf( "Value after C2 incremention: " );
            printf( "%d\n", *shared_int );
            break;

        default:
            // parent
            waitpid( pid2, NULL, 0 );
            break;
    }  //end switch

    return 0;
}

The output from the above code (which is consistent between runs):

Value after C1 incremention: 5000
Value after P1 incremention: 15000
Value after C2 incremention: 20000

Note: the GNU C library describes a pid_t as:

The pid_t data type is a signed integer type which is capable of representing a process ID. In the GNU C Library, this is an int.

user3629249
  • 16,402
  • 1
  • 16
  • 17