Child processes (fork() ?)

I understand using the "fork()" function to create a child process. I understand that the fork() function returns the PID of the child process. However, can fork() be used to create a different child process? I thought forking was when a process created a copy of itself. If so, can I not use fork to open a new process? I don't want to use system() for several reasons.

Finally, how can I check if the process has finished?

Thanks...

Essentially, what I want to do is run another process, given the location (probably through input), wait for it to finish, and then print the result (return value). I'd also like to be able to print it's PID and destroy the process if I want to, but that won't be hard because fork() returns the PID...

Edit: I'm thinking instead I could use execl() and fork() to somehow get execl() to replace the child process instead. How could I do that?
Last edited on
Thanks, that might be it. :)
I think I've almost got it:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
#include "executor.h"

int exec(std::string path)  {
    pid_t PID = 0;
    int result = 0, filedes[2], status;

    /* Create child process: */
    PID = fork();

    if (PID == -1) {
        return 1;
    }

    result = pipe(filedes);

    if (PID != 0) {
        std::cout << "Created child process. PID: " << PID << "\n";
    }

    if (result == -1) {
        return 1;
    }

    if (PID == 0) { // This is the child process
        std::string prog = "./" + path;
        execvp(prog.c_str(), NULL); // Execute the program
    } else { // This is the parent process
        waitpid(PID, &status, 0); // Wait for the child process to return.
        std::cout << "Process returned " << WEXITSTATUS(status) << ".\n";
        std::cout << "Press enter.\n";
    }

    return 0;
}


I think it works, it gets the return value but the program I try to run doesn't seem to be able to do anything.

Will I need to use the pipe I have with the child process to let it interact with the terminal?
No, never mind, it works :)
Yay, now I have one of those things that some IDEs have (such as Code::Blocks).
Last edited on
closed account (S6k9GNh0)
Why not pid_t PID = fork(); ? And how does int result = 0, filedes[2], status; work?
Last edited on
Remember that it is possible (though unlikely) that line 26 will fail. If it does, you need to exit() with an appropriate error code so that line 28 will notice that the child failed.
Could I do this:
1
2
3
4
int myInt = execvp(prog.c_str(), NULL);
if (myInt) {
    return myInt;
}
?
And why might line 26 fail? Just because functions sometimes do, or because of the way I'm using it?

Computerquip, I'm not sure why I declared PID before using it. I guess I'm still in C mode from the other day.
Also, you can declare multiple variables on the same type on a single line, they have to be comma separated.
execvp will fail for any reason the man page says it will fail. For example, the program was
not found, was not executable, you didn't have permission to execute it, you had too many
running processes already, etc. etc.

execvp() returns only upon failure. On success it never returns.

1
2
3
execvp( prog.c_str(), 0 );
std::cout << "Aiiie, execvp failed, errno = " << errno << std::endl;
exit( 1 );


Oh... ok. How do I check if it succeeded then? I'd like to know the return value of the process.

Thanks.
If it fails, the parent will see the return value 1 (whatever you pass to exit()). If it
succeeds, the parent will see whatever return value the child's main() returns.

You can look up any of the waitpid() functions (there are several) to know when the
child exit and receive the return code or you can register a signal handler for SIGCHLD
(which you'll receive the when child exits) and then do a non-blocking waitpid()).
I think I've got it. It works, at least.

I tried to keep function sizes down. Hopefully, it doesn't detract from legibility.

I'm not sure if I'm implementing execvp() right. It works, but I'd still like to know it's not going to be bugged.
When I used your above example (with a different error message, although "Aiiie!" made me laugh) the error message was always showing...

If you're wondering why I used FILENAME_MAX for the hostname[] buffer's size, I couldn't think of anything better to set it to, and I wanted to hurry up and test the program.

Example output at the bottom, showing it working.

Finally, sorry for huge post.

runner.h
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
#ifndef _RUNNER_H_
#define _RUNNER_H_

// STL includes
#include <iostream> // For IO and std::strings
#include <cstdlib>  // For exit()
#include <cerrno>   // For errno
// Other includes (for fork(), pipe() and waitpid())
#include <unistd.h>
#include <sys/wait.h>
#include <sys/types.h>
#include <signal.h>   // For kill() to work

// Macros:
// None

// Globals:
// None

// Function prototypes

// In main.cpp:
int  main(int, char**);     // Entry point
void error(std::string);    // Prints a given error message to the cerr stream, for consistency.

// In sort.cpp:
int  sort(int, char**);     // Sorts argv and calls an appropriate function
int search(std::string, std::string, int, int);
                            // Searches for parameters in two strings
// In exec.cpp:
void exec(std::string);     // Executes a given program
#endif 


main.cpp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
#include "runner.h"

int main(int argc, char* argv[]) {
    if (argc < 4) {
        std::string path;
        char cwd[FILENAME_MAX], hostname[FILENAME_MAX];

        if (!getcwd(cwd, FILENAME_MAX)) {
            error("failed to receive current working directory.");
        }

        if (gethostname(hostname, FILENAME_MAX)) {
            error("failed to get hostname.");
        }

        std::cout << getlogin() << "@" << hostname << ":" << cwd << "\n";

        std::cout << "Path: ";

        getline(std::cin, path);

        exec(path);

    } else {
        return sort(argc, argv); // Sort arguments
    }

    return 0;
}

void error(std::string errmsg) {
    std::cerr << "Error " << errno << " - " << errmsg << "\n";
}


exec.cpp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
#include "runner.h"

void exec(std::string path) {
    std::cout << "Creating child process...";

    pid_t pid = fork();

    if (pid == -1) {
        error("error creating child process, exiting...");
        exit(1);
    } else if (pid == 0) { // This is the child process

        std::string program;

        if (path[0] == '/') { // Assume program is in another directory
            program = path;
        } else { // Assume program is in current working directory
            program = "./" + path;
        }

        if (execvp(program.c_str(), 0)) { // execvp failed
            error("fatal - execvp failed!");
            exit(1);
        }

    } else { // This is the parent process.

        int status;
        std::cout << " done. PID: " << pid << ".\n";

        double start = 0;
        waitpid(pid, &status, 0); // Wait for program to finish executing.
        double dur = (clock() - start) / CLOCKS_PER_SEC; // Get execution time

        std::cout << "Program returned " << WEXITSTATUS(status);
        std::cout << " and lasted " << dur << " seconds.\nPress enter.\n";
        std::cin.get();
    }
}


sort.cpp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
#include "runner.h"

int sort(int argc, char* argv[]) {
    std::string thisArg, nextArg;

    for (int i = 0; i < argc; ++i) {
        thisArg = argv[i]; nextArg = argv[i + 1];

        return search(thisArg, nextArg, i, argc); // Find values in parameters
    }

    return 0;
}

int search(std::string thisArg, std::string nextArg, int i, int argc) {

    if (thisArg.find("-e") != std::string::npos) {
        if (i + 1 != argc) { // Check we have another parameter to find
            exec(nextArg);
            return 0;
        } else {
            error("bad argument " + thisArg + " exiting.");
            return 1;
        }

    } else if (thisArg.find("-k") != std::string::npos) {

        if (i + 1 != argc) {
            std::cout << "Kill function not yet implemented.\n";
            return 0;
        } else {
            error("bad argument " + thisArg + " exiting.");
            return 1;
        }

    }  else if (thisArg.find("-h") != std::string::npos ||
                thisArg.find("help") != std::string::npos) { // Show help message
        help();
    } else { // No arguments were recognised
        error("no usable arguments found...");
        return 1;
    }

    return 0;
}


Example output:
$ ./Runner
chris@chris:/home/chris/Projects/C++/Runner/bin/Release
Path:
Runner
Creating child process... done. PID: 7655.
chris@chris:/home/chris/Projects/C++/Runner/bin/Release
Path:
/bin/bash
Creating child process... done. PID: 7656.
chris@chris:~/Projects/C++/Runner/bin/Release$ which bash
/bin/bash
chris@chris:~/Projects/C++/Runner/bin/Release$ exit
exit
Program returned 0 and lasted 0 seconds.
Press enter.

Program returned 0 and lasted 0 seconds.
Press enter.


As you can see the timing doesn't work... I certainly did not type in all that in < 1 second...
Last edited on
I was thinking I could use another child process to do the timing. It would be on an infinite loop with an iterator, and it would sleep for 1 second before incrementing the iterator.
When the first child returns, I would send it a SIGKILL or something and it would return how many iterations (how many seconds) it had done.

The problem for that is if the program lasted more than 4 or so minutes it would return 0 (apparently the maximum value a program can return is 255 and 5 minutes is 300 seconds).

So I thought of another way. I could maybe send the child a signal, but not SIGKILL - instead, I would send it another signal, and upon receiving this signal it would print how long it had been looping for.

How could I send/receive signals?

My other alternative would be to use pipes, but as I've never done anything with signals, and I know how to use pipes already, I think it will be better to use signals.

Thanks.

I just decided to incorporate this into a larger project. As it is able to work via command line arguments; I thought it would be fun to create a simple shell.
Last edited on
Topic archived. No new replies allowed.