execl("/usr/bin/less", "less", (char*) 0);
(*) Introduction
I first wrote a program to fork and make parent read from the child.
The child would invoke cat to write a file to stdout and the parent
would invoke less to let me read the file on the screen. Everything
worked as expected and I had no questions about it.
(*) New experiment
So I decided to reverse their roles --- make the child run less and the parent cat the file. But before getting there, let's try something
easier --- instead of cat'ting the file, the parent just invokes write()
on the write-end of the pipe. That's a little simpler because we don't
call execl() and don't need to dup2-file-descriptors, say.
(*) Unexpected result
This first attempt at reversing these roles produces no effect.
--8<---------------cut here---------------start------------->8--- %./parent-is-proc-and-child-is-less.exe
%
--8<---------------cut here---------------end--------------->8---
What is my misunderstanding here? The full program is below with a lot
of commentary so you can see what's on my mind. Thank you!
(*) The program
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
//--8<---------------cut here---------------start------------->8---
// helpers
void syserr_die(char* msg) { perror(msg); exit(1); }
void pipe_die(int *fd) {
int r; r = pipe(fd); if (r < 0) { syserr_die("pipe"); }
}
pid_t fork_die() {
int p; p = fork(); if (p < 0) { syserr_die("fork"); }
return p;
}
void dup2_die(int from, int to, char *msg) {
int r;
r = dup2(from, to); if (r < 0) syserr_die(msg);
}
//--8<---------------cut here---------------end--------------->8---
int main() {
int fd[2]; pid_t pid;
pipe_die(&fd[0]);
/*
At this point I have a reading file descriptor at fd[0] and a
writing one at fd[1].
*/
pid = fork();
if (pid < 0) {
syserr_die("fork");
}
if (pid == 0) {
/*
I run /less/, so I must read from the pipe and not write to it.
So I close fd[1] right away because I don't need it.
*/
close(fd[1]);
/*
It is /less/ that will do the reading and it does it from STDIN,
so I must ask system to make this replacement for me. Let's
create the following vocabulary: move fd[0] to STDIN, that is,
to 0.
*/
dup2_die(fd[0], 0, "child dup2"); /* Let stdin be fd[0]. */
/*
When /less/ starts reading, it will be reading bytes written to
the pipe. But it's important to notice that the pipe is an
object in the kernel, not in our program. So fd[0] is not the
pipe; it's just that number that refers to that pipe. The
number 0 (which refers to STDIN) is already referring to the
pipe, so we don't need a second reference --- so we destroy
fd[0].
*/
close(fd[0]);
/*
We are ready to run less now. The binary /less/ will be loaded in
memory and what it finds as its kernel-context is everything we
prepared above. We are done with the parent.
*/
execl("/usr/bin/less", "less", (char*) 0);
syserr_die("execl of less");
/*
I keep running until /less/ is dead.
*/
}
/* I'm the writer. My job is to just write data to fd[1] ---
which is the writing end of the pipe. I don't need to read
anything from the STDIN, so I begin by closing it. I will
write to fd[1], not to STDOUT or STDERR. So close them too.
*/
close(0); close(1); close(2);
/*
I'm going to write to fd[1]. I have no business with fd[0].
*/
close(fd[0]);
for (int i = 1; i <= 200; ++i) {
char s[1000];
sprintf(s, "i = %d\n", i);
write(fd[1], s, strlen(s));
}
/*
My job is done. I'm dead by now.
*/
}
(*) Introduction
I first wrote a program to fork and make parent read from the child.
The child would invoke cat to write a file to stdout and the parent
would invoke less to let me read the file on the screen. Everything
worked as expected and I had no questions about it.
(*) New experiment
So I decided to reverse their roles --- make the child run less and the parent cat the file. But before getting there, let's try something
easier --- instead of cat'ting the file, the parent just invokes write()
on the write-end of the pipe. That's a little simpler because we don't
call execl() and don't need to dup2-file-descriptors, say.
(*) Unexpected result
This first attempt at reversing these roles produces no effect.
Furher, the parent needs to wait because otherwise, the
process running less becomes orphaned and will be killed (via SIGTTOU)
when it tries to access the terminal.
The shell runs each command in its own process
group. The parent process of the less is the process group leader of
this process group and the one the controlling terminal is associated
with. As soon as it exits, this controlling terminal becomes free for
reuse.
The less will then be a background process running in an orphaned
process group (as the process group leader has exited) and thus, the
kernel will prevent it from using the terminal by making the system call
fail with EIO
(Linux, different on other systems).
Rainer Weikusat wrote:
The shell runs each command in its own process
group. The parent process of the less is the process group leader of
this process group and the one the controlling terminal is associated
with. As soon as it exits, this controlling terminal becomes free for
reuse.
No, the controlling terminal doesn't become free until the shell (as
session leader) exits.
The less will then be a background process running in an orphaned
process group (as the process group leader has exited) and thus, the
kernel will prevent it from using the terminal by making the system call
fail with EIO
Correct.
(Linux, different on other systems).
How do other systems differ? The behaviour you described is what POSIX requires. XBD 11.1.4 Terminal Access Control:
If TOSTOP is set, the process group of the writing process is
orphaned, the writing process is not ignoring the SIGTTOU signal,
and the writing thread is not blocking the SIGTTOU signal, the
write() shall return -1, with errno set to [EIO] and no signal
shall be sent.
On Thu, 04 Nov 2021 12:58:35 -0300, Meredith Montgomery wrote:
Your program mostly works, but you need one small change. See below
(*) Introduction
I first wrote a program to fork and make parent read from the child.
The child would invoke cat to write a file to stdout and the parent
would invoke less to let me read the file on the screen. Everything
worked as expected and I had no questions about it.
(*) New experiment
So I decided to reverse their roles --- make the child run less and the
parent cat the file. But before getting there, let's try something
easier --- instead of cat'ting the file, the parent just invokes write()
on the write-end of the pipe. That's a little simpler because we don't
call execl() and don't need to dup2-file-descriptors, say.
(*) Unexpected result
This first attempt at reversing these roles produces no effect.
--8<---------------cut here---------------start------------->8---
%./parent-is-proc-and-child-is-less.exe
%
--8<---------------cut here---------------end--------------->8---
What is my misunderstanding here? The full program is below with a lot
of commentary so you can see what's on my mind. Thank you!
(*) The program
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
//--8<---------------cut here---------------start------------->8---
// helpers
IMHO, these "helper" functions are less than helpfull, in this program.
When debugging a program that you are using to learn a new technique,
it helps to have all the relevant logic in plain sight; helper functions
hide logic from sight, making it harder to determine if a flaw is in
your primary logic, or in the "helper" functions. Helper functions,
if inexpertly used, also obfuscate the logical flow of the program,
hiding more logic errors. Just a thought from a guy that spent 30 years designing, writing, debugging, and modifying complex programs for a
living.
void syserr_die(char* msg) { perror(msg); exit(1); }
void pipe_die(int *fd) {
int r; r = pipe(fd); if (r < 0) { syserr_die("pipe"); }
}
pid_t fork_die() {
int p; p = fork(); if (p < 0) { syserr_die("fork"); }
return p;
}
void dup2_die(int from, int to, char *msg) {
int r;
r = dup2(from, to); if (r < 0) syserr_die(msg);
}
//--8<---------------cut here---------------end--------------->8---
int main() {
int fd[2]; pid_t pid;
pipe_die(&fd[0]);
/*
At this point I have a reading file descriptor at fd[0] and a
writing one at fd[1].
*/
pid = fork();
if (pid < 0) {
syserr_die("fork");
}
if (pid == 0) {
/*
I run /less/, so I must read from the pipe and not write to it.
So I close fd[1] right away because I don't need it.
*/
close(fd[1]);
/*
It is /less/ that will do the reading and it does it from STDIN,
so I must ask system to make this replacement for me. Let's
create the following vocabulary: move fd[0] to STDIN, that is,
to 0.
*/
dup2_die(fd[0], 0, "child dup2"); /* Let stdin be fd[0]. */
/*
When /less/ starts reading, it will be reading bytes written to
the pipe. But it's important to notice that the pipe is an
object in the kernel, not in our program. So fd[0] is not the
pipe; it's just that number that refers to that pipe. The
number 0 (which refers to STDIN) is already referring to the
pipe, so we don't need a second reference --- so we destroy
fd[0].
*/
close(fd[0]);
/*
We are ready to run less now. The binary /less/ will be loaded in
memory and what it finds as its kernel-context is everything we
prepared above. We are done with the parent.
*/
execl("/usr/bin/less", "less", (char*) 0);
syserr_die("execl of less");
/*
I keep running until /less/ is dead.
*/
}
/* I'm the writer. My job is to just write data to fd[1] ---
which is the writing end of the pipe. I don't need to read
anything from the STDIN, so I begin by closing it. I will
write to fd[1], not to STDOUT or STDERR. So close them too.
*/
close(0); close(1); close(2);
There's no need to close() stderr, and if you don't, then you gain the (dubious, to some) ability to insert debugging fprintf(stderr,...)
statements into the code to trace execution or track down bugs.
In fact, given that your logic write()'s to the write end of the
pipe, you don't really have to close() stdin or stdout either.
It's sometimes helpful to have these around.
/*
I'm going to write to fd[1]. I have no business with fd[0].
*/
close(fd[0]);
for (int i = 1; i <= 200; ++i) {
char s[1000];
sprintf(s, "i = %d\n", i);
write(fd[1], s, strlen(s));
A (bit) better approach here might have been to use the length returned
by sprintf(3) as the write() count parameter.
However, if, instead of closing stdout, you had
dup2(STDOUT_FILENO,fd[1])
then you could have
printf("i = %d\n",i);
directly to your pipe output.
}
Now, if your parent process terminates now, it closes the write end of the pipe before the child has read it to the end. From the pipe(7) manual page:
"If all file descriptors referring to the write end of a pipe have been
closed, then an attempt to read(2) from the pipe will see end-of-file
(read(2) will return 0)"
You need to wait(2) here for the child to complete it's processing.
/*
My job is done. I'm dead by now.
*/
}
Meredith Montgomery <mmontgomery@levado.to> writes:
(*) Introduction
I first wrote a program to fork and make parent read from the child.
The child would invoke cat to write a file to stdout and the parent
would invoke less to let me read the file on the screen. Everything
worked as expected and I had no questions about it.
(*) New experiment
So I decided to reverse their roles --- make the child run less and the
parent cat the file. But before getting there, let's try something
easier --- instead of cat'ting the file, the parent just invokes write()
on the write-end of the pipe. That's a little simpler because we don't
call execl() and don't need to dup2-file-descriptors, say.
(*) Unexpected result
This first attempt at reversing these roles produces no effect.
Working program (with the noise comments removed):
----
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <sys/wait.h>
//--8<---------------cut here---------------start------------->8---
// helpers
void syserr_die(char* msg) { perror(msg); exit(1); }
void pipe_die(int *fd) {
int r; r = pipe(fd); if (r < 0) { syserr_die("pipe"); }
}
pid_t fork_die() {
int p; p = fork(); if (p < 0) { syserr_die("fork"); }
return p;
}
void dup2_die(int from, int to, char *msg) {
int r;
r = dup2(from, to); if (r < 0) syserr_die(msg);
}
//--8<---------------cut here---------------end--------------->8---
int main() {
int fd[2]; pid_t pid;
pipe_die(&fd[0]);
pid = fork();
if (pid < 0) {
syserr_die("fork");
}
if (pid == 0) {
close(fd[1]);
dup2_die(fd[0], 0, "child dup2"); /* Let stdin be fd[0]. */
close(fd[0]);
execl("/usr/bin/less", "less", (char*) 0);
syserr_die("execl of less");
}
close(fd[0]);
for (int i = 1; i <= 200; ++i) {
char s[1000];
sprintf(s, "i = %d\n", i);
write(fd[1], s, strlen(s));
}
close(fd[1]);
wait(NULL);
}
----------
fd[1] needs to be closed here because otherwise, less never sees an EOF
and thus, blocks forever expecting input which won't ever
arrive. Furher, the parent needs to wait because otherwise, the
process running less becomes orphaned and will be killed (via SIGTTOU)
when it tries to access the terminal.
Rainer Weikusat <rweikusat@talktalk.net> writes:
I looked up the section ``Orphaned Process Groups'' and, for the record, here's what it says.
(*) Orphaned Process Groups
When a controlling process terminates, its terminal becomes free and a
new session can be established on it. (In fact, another user could log
in on the terminal.) This could cause a problem if any processes from
the old session are still trying to use that terminal.
To prevent problems, process groups that continue running even after the session leader has terminated are marked as orphaned process groups.
When a process group becomes an orphan, its processes are sent a SIGHUP signal.
Lew Pitcher <lew.pitcher@digitalfreehold.ca> writes:[snip]
You need to wait(2) here for the child to complete it's processing.
But I think I must also close fd[1] like Rainer Weikusat said.
(Otherwise /less/ blocks on a read(2) call.)
/*
My job is done. I'm dead by now.
*/
}
Geoff Clare <geoff@clare.See-My-Signature.invalid> writes:
How do other systems differ? The behaviour you described is what POSIX
requires. XBD 11.1.4 Terminal Access Control:
If TOSTOP is set, the process group of the writing process is
orphaned, the writing process is not ignoring the SIGTTOU signal,
and the writing thread is not blocking the SIGTTOU signal, the
write() shall return -1, with errno set to [EIO] and no signal
shall be sent.
Glibc documentation claims they do:
When a process in an orphaned process group (*note Orphaned
Process Groups::) receives a 'SIGTSTP', 'SIGTTIN', or 'SIGTTOU'
signal and does not handle it, the process does not stop.
Stopping the process would probably not be very useful, since
there is no shell program that will notice it stop and allow the
user to continue it. What happens instead depends on the
operating system you are using. Some systems may do nothing;
others may deliver another signal instead, such as 'SIGKILL' or
'SIGHUP'.
[at the and of 24.2.5 Job Control Signals]
Lew Pitcher <lew.pitcher@digitalfreehold.ca> writes:
[...]
/*
** The parent needs to wait because otherwise, the process running
** less(1) becomes orphaned and will be killed (via SIGTTOU) when
** it tries to access the terminal.
** (Rainer Weikusat <rweikusat@talktalk.net>)
*/
This is unfortunately almost completely wrong: The process becomes
orphaned but this only means that it becomes a child of init (whatever
that's called in the FatRat Lunix version du jour). The important bit
here is that the process group becomes orphaned. When processes
belonging to it which don't block or ignore SIGTTOU try to write to
terminal, the system call will fail with EIO. That's presumably what terminates the less here.
/*
** The parent needs to wait because otherwise, the process running
** less(1) becomes orphaned and will be killed (via SIGTTOU) when
** it tries to access the terminal.
** (Rainer Weikusat <rweikusat@talktalk.net>)
*/
I spent a bit of time to write a one-off that both performs the
requisite task correctly, /and/ embodies the stylistic points that
I made in the prior post. This is what I came up with...
/******************* Code begins **********************************/
/*
** This program demonstrates a solution to a problem posted by
** Meredith Montgomery in the comp.unix.programmer usenet group.
**
** The programmer wants a program that creates a pipe between
** itself and the stdin of a child process. The child process
** runs less(1) on it's stdin (the read end of the pipe) while
** the parent process generates print data to the write end of
** the pipe.
**
** The development of this program followed "Occam's razor",
** in that it subscribed to the principle that "entities
** should not be multiplied beyond necessity". In this case,
** I endeavoured to avoid needless encapsulation and abstraction
** of logic, in order to create a simple working version of the
** solution. Embellishments, such as logic encapsulation and
** isolation, are left to others to implement.
**
** == Evolution ==
** 0: basic framework
** 1: create pipe
** 2: fork
** 3: child process reads input from pipe
** 4: parent process output to pipe
** 5: document finer points in comments
*/
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <errno.h>
#include <string.h>
#include <sys/types.h>
#include <sys/wait.h>
int main(int argc, char *argv[])
{
int status = EXIT_FAILURE;
int pfd[2];
/*
** Step 1, make our pipe
*/
if (pipe(pfd) == 0) /* On error, pipe(2) will return -1 and set errno */
{
/*
** Step 2: we got a valid pipe pair, now launch child and perform all processing
*/
switch (fork())
{
/*
** On error, fork(2) will return -1, and set errno appropriately
*/
case -1: /* fork failed */
fprintf(stderr,"%s: fork() failed - %s\n",argv[0],strerror(errno));
break;
/*
** In the child process, fork(2) returns 0
*/
case 0: /* CHILD process */
/*
** Step 3: prepare the child process to read the pipe through stdin
** and run "less" on the input
*/
close(pfd[1]); /* don't hold pipe write end open */
if (dup2(pfd[0],STDIN_FILENO) != -1)
{
close(pfd[0]); /* close the now useless fd */
execl("/usr/bin/less","less",NULL); /* use "less" to process stdin */
/* we only get here if execl() failed */
fprintf(stderr,"%s: execl() failed - %s\n",argv[0],strerror(errno));
}
else fprintf(stderr,"%s: child dup2() failed - %s\n",argv[0],strerror(errno));
break;
/*
** In the parent process, fork(2) returns the non-zero, non -1 PID
** of the child process
*/
default: /* PARENT process */
/*
** Step 4: prepare the parent process to write the pipe through stdout
** write some data so that the child can process it, wait for
** the child to process the data and then terminate the parent.
*/
close(pfd[0]); /* dont hold pipe read end open */
if (dup2(pfd[1],STDOUT_FILENO) != -1) /* use the write end of pipe as stdout */
{
close(pfd[1]); /* close the now useless fd */
/* generate some output to be piped to the child */
for (int count = 0; count < 100; ++count)
printf("This is generated line number %d\n",count);
fclose(stdout); /* signals EOF to child process stdin */
/*
** The parent needs to wait because otherwise, the process running
** less(1) becomes orphaned and will be killed (via SIGTTOU) when
** it tries to access the terminal.
** (Rainer Weikusat <rweikusat@talktalk.net>)
*/
wait(NULL); /* wait for the child to finish */
/*
** The parent needs to wait because otherwise, the process running
** less(1) becomes orphaned and will be killed (via SIGTTOU) when
** it tries to access the terminal.
** (Rainer Weikusat <rweikusat@talktalk.net>)
*/
status = EXIT_SUCCESS; /* Mission Accomplished! */
}
else fprintf(stderr,"%s: parent dup2() failed - %s\n",argv[0],strerror(errno));
break;
}
}
else fprintf(stderr,"%s: pipe() failed - %s\n",argv[0],strerror(errno));
return status;
}
/******************* Code ends **********************************/
I have a question about Lew Pitcher's program. I still don't know what
else must be done if we replace the printf() calls with an execution of
cat. I seem to understand the original program completely, but my >understanding must be wrong somewhere because I can't make a simple >modification and predict the output. Please see the details below.
(Notice I paste the full program at the end of the message just in case
it helps anyone to copy it and compile it.)
/* generate some output to be piped to the child */
for (int count = 0; count < 100; ++count)
printf("This is generated line number %d\n",count);
fclose(stdout); /* signals EOF to child process stdin */
What happens if I replace this for-loop with an execution of cat? For >example, what happens if we place this entire for-loop with
execl("/usr/bin/cat", "cat", "lew.c", NULL)?
Assume we remove the fclose and the wait below, although I think that
has no effect if execl succeeds.
I have a question about Lew Pitcher's program. I still don't know what
else must be done if we replace the printf() calls with an execution of
cat. I seem to understand the original program completely, but my understanding must be wrong somewhere because I can't make a simple modification and predict the output. Please see the details below.
(Notice I paste the full program at the end of the message just in case
it helps anyone to copy it and compile it.)
* Lew Pitcher <lew.pitcher@digitalfreehold.ca>
| However, when the child process runs less(2), other things happen. /If/ | we had forked off /usr/bin/cat instead of /usr/bin/less, we could have | forgone the wait(2). But, /usr/bin/less expects to have control of the | controlling terminal (where /usr/bin/cat does not), and once our parent | process terminates, control of the controlling terminal is given back
| to the shell.
I always thought that fork() duplicates the current process with 'everything', so I would have expected the child (which eventually execs "less") to also have "control of the controlling terminal".
But obviously there is more to that...
* Lew Pitcher <lew.pitcher@digitalfreehold.ca>
| However, when the child process runs less(2), other things happen. /If/ >| we had forked off /usr/bin/cat instead of /usr/bin/less, we could have >| forgone the wait(2). But, /usr/bin/less expects to have control of the >| controlling terminal (where /usr/bin/cat does not), and once our parent >| process terminates, control of the controlling terminal is given back
| to the shell.
I always thought that fork() duplicates the current process with >'everything', so I would have expected the child (which eventually execs >"less") to also have "control of the controlling terminal".
But obviously there is more to that...
Lew Pitcher <lew.pitcher@digitalfreehold.ca> writes:
On Thu, 03 Mar 2022 10:15:27 +0100, Ralf Fassel wrote:
* Lew Pitcher <lew.pitcher@digitalfreehold.ca>
| However, when the child process runs less(2), other things happen. /If/
| we had forked off /usr/bin/cat instead of /usr/bin/less, we could have
| forgone the wait(2). But, /usr/bin/less expects to have control of the
| controlling terminal (where /usr/bin/cat does not), and once our parent
| process terminates, control of the controlling terminal is given back >>> | to the shell.
I always thought that fork() duplicates the current process with
'everything', so I would have expected the child (which eventually execs >>> "less") to also have "control of the controlling terminal".
But obviously there is more to that...
Well, /every/ process that has not divorced itself from it's controlling >>terminal has control of that terminal, in that every process can read from it,
write to it, and change it's characteristics (line echo, etc.).
I still don't understand the intricacies of it myself, but in it's simplest >>form, imagine if two processes both are trying to read from the controlling >>terminal: which process gets the input?
"If a process is in the foreground process group of its controlling
terminal, read operations shall be allowed, as described in Input
Processing and Reading Data. Any attempts by a process in a background
process group to read from its controlling terminal cause its process
group to be sent a SIGTTIN signal unless one of the following special
cases applies: if the reading process is ignoring the SIGTTIN signal
or the reading thread is blocking the SIGTTIN signal, or if the
process group of the reading process is orphaned, the read() shall
return -1, with errno set to [EIO] and no signal shall be sent. The
default action of the SIGTTIN signal shall be to stop the process
to which it is sent."
"If a process is in the foreground process group of its controlling
terminal, write operations shall be allowed as described in Writing
Data and Output Processing. Attempts by a process in a background
process group to write to its controlling terminal shall cause the
process group to be sent a SIGTTOU signal unless one of the following
special cases applies: if TOSTOP is not set, or if TOSTOP is set and
the process is ignoring the SIGTTOU signal or the writing thread is
blocking the SIGTTOU signal, the process is allowed to write to the
terminal and the SIGTTOU signal is not sent. If TOSTOP is set, the
process group of the writing process is orphaned, the writing process
is not ignoring the SIGTTOU signal, and the writing thread is not blocking
the SIGTTOU signal, the write() shall return -1, with errno set to [EIO]
and no signal shall be sent."
This implies that multiple processes _within_ a process group need to coordinate both read and write operations to avoid conflicting with
each other
(regardless of the type of file - block special, character
special, fifo, pipe or regular file).
On Thu, 03 Mar 2022 10:15:27 +0100, Ralf Fassel wrote:
* Lew Pitcher <lew.pitcher@digitalfreehold.ca>
| However, when the child process runs less(2), other things happen. /If/
| we had forked off /usr/bin/cat instead of /usr/bin/less, we could have >> | forgone the wait(2). But, /usr/bin/less expects to have control of the >> | controlling terminal (where /usr/bin/cat does not), and once our parent
| process terminates, control of the controlling terminal is given back >> | to the shell.
I always thought that fork() duplicates the current process with
'everything', so I would have expected the child (which eventually execs
"less") to also have "control of the controlling terminal".
But obviously there is more to that...
Well, /every/ process that has not divorced itself from it's controlling >terminal has control of that terminal, in that every process can read from it, >write to it, and change it's characteristics (line echo, etc.).
I still don't understand the intricacies of it myself, but in it's simplest >form, imagine if two processes both are trying to read from the controlling >terminal: which process gets the input?
On Thu, 03 Mar 2022 16:23:53 +0000, Scott Lurndal wrote:
Lew Pitcher <lew.pitcher@digitalfreehold.ca> writes:
On Thu, 03 Mar 2022 10:15:27 +0100, Ralf Fassel wrote:
* Lew Pitcher <lew.pitcher@digitalfreehold.ca>
| However, when the child process runs less(2), other things happen. /If/
| we had forked off /usr/bin/cat instead of /usr/bin/less, we could have
| forgone the wait(2). But, /usr/bin/less expects to have control of the
| controlling terminal (where /usr/bin/cat does not), and once our parent
| process terminates, control of the controlling terminal is given back
| to the shell.
I always thought that fork() duplicates the current process with
'everything', so I would have expected the child (which eventually execs >>>> "less") to also have "control of the controlling terminal".
But obviously there is more to that...
Well, /every/ process that has not divorced itself from it's controlling >>>terminal has control of that terminal, in that every process can read from it,
write to it, and change it's characteristics (line echo, etc.).
I still don't understand the intricacies of it myself, but in it's simplest >>>form, imagine if two processes both are trying to read from the controlling >>>terminal: which process gets the input?
"If a process is in the foreground process group of its controlling
terminal, read operations shall be allowed, as described in Input
Processing and Reading Data. Any attempts by a process in a background
process group to read from its controlling terminal cause its process
group to be sent a SIGTTIN signal unless one of the following special
cases applies: if the reading process is ignoring the SIGTTIN signal
or the reading thread is blocking the SIGTTIN signal, or if the
process group of the reading process is orphaned, the read() shall
return -1, with errno set to [EIO] and no signal shall be sent. The
default action of the SIGTTIN signal shall be to stop the process
to which it is sent."
"If a process is in the foreground process group of its controlling
terminal, write operations shall be allowed as described in Writing
Data and Output Processing. Attempts by a process in a background
process group to write to its controlling terminal shall cause the
process group to be sent a SIGTTOU signal unless one of the following
special cases applies: if TOSTOP is not set, or if TOSTOP is set and
the process is ignoring the SIGTTOU signal or the writing thread is
blocking the SIGTTOU signal, the process is allowed to write to the
terminal and the SIGTTOU signal is not sent. If TOSTOP is set, the
process group of the writing process is orphaned, the writing process
is not ignoring the SIGTTOU signal, and the writing thread is not blocking
the SIGTTOU signal, the write() shall return -1, with errno set to [EIO] >> and no signal shall be sent."
This implies that multiple processes _within_ a process group need to
coordinate both read and write operations to avoid conflicting with
each other
And, if they don't co-ordinate read and write operations, then....
the unpredictable happens.
Just like Meredith is seeing with the various iterations of her program, >where both less(1) and the shell compete for input (and output) from
the controlling terminal.
Lew Pitcher <lew.pitcher@digitalfreehold.ca> writes:
On Thu, 03 Mar 2022 16:23:53 +0000, Scott Lurndal wrote:
Lew Pitcher <lew.pitcher@digitalfreehold.ca> writes:
On Thu, 03 Mar 2022 10:15:27 +0100, Ralf Fassel wrote:
* Lew Pitcher <lew.pitcher@digitalfreehold.ca>
| However, when the child process runs less(2), other things
| happen. /If/
| we had forked off /usr/bin/cat instead of /usr/bin/less, we
| could have
| forgone the wait(2). But, /usr/bin/less expects to have
| control of the
| controlling terminal (where /usr/bin/cat does not), and
| once our parent
| process terminates, control of the controlling terminal is given back
| to the shell.
I always thought that fork() duplicates the current process with
'everything', so I would have expected the child (which eventually execs >>>>> "less") to also have "control of the controlling terminal".
But obviously there is more to that...
Well, /every/ process that has not divorced itself from it's controlling >>>>terminal has control of that terminal, in that every process can
read from it,
write to it, and change it's characteristics (line echo, etc.).
I still don't understand the intricacies of it myself, but in it's simplest >>>>form, imagine if two processes both are trying to read from the controlling >>>>terminal: which process gets the input?
"If a process is in the foreground process group of its controlling
terminal, read operations shall be allowed, as described in Input
Processing and Reading Data. Any attempts by a process in a background >>> process group to read from its controlling terminal cause its process >>> group to be sent a SIGTTIN signal unless one of the following special >>> cases applies: if the reading process is ignoring the SIGTTIN signal
or the reading thread is blocking the SIGTTIN signal, or if the
process group of the reading process is orphaned, the read() shall
return -1, with errno set to [EIO] and no signal shall be sent. The
default action of the SIGTTIN signal shall be to stop the process
to which it is sent."
"If a process is in the foreground process group of its controlling
terminal, write operations shall be allowed as described in Writing
Data and Output Processing. Attempts by a process in a background
process group to write to its controlling terminal shall cause the
process group to be sent a SIGTTOU signal unless one of the following >>> special cases applies: if TOSTOP is not set, or if TOSTOP is set and
the process is ignoring the SIGTTOU signal or the writing thread is
blocking the SIGTTOU signal, the process is allowed to write to the
terminal and the SIGTTOU signal is not sent. If TOSTOP is set, the
process group of the writing process is orphaned, the writing process >>> is not ignoring the SIGTTOU signal, and the writing thread is not blocking
the SIGTTOU signal, the write() shall return -1, with errno set to [EIO] >>> and no signal shall be sent."
This implies that multiple processes _within_ a process group need to
coordinate both read and write operations to avoid conflicting with
each other
And, if they don't co-ordinate read and write operations, then....
the unpredictable happens.
Just like Meredith is seeing with the various iterations of her program, >>where both less(1) and the shell compete for input (and output) from
the controlling terminal.
It gets even worse if both processes are sharing a file descriptor
but are using different FILE objects to access that file descriptor
due to the stdio buffering (avoided by disabling buffering with setvbuf(3)).
My exercise, therefore, needs another fork if I intend for the parent
process to exec cat because cat won't wait for the child and so the
child might lose its parent and, therefore, losing rights to talk to the terminal. Forking again before running cat allows the parent to wait
for both cat and less, keeping both programs with the right to talk to
the terminal. So it seems I may conclude that for a situation such as
cat --> less
we really need three processes, one for cat, one for less and one parent
for waiting for both to avoid any of them from losing the right to talk
to the terminal (assuming any of them might try to). In general, a
pipeline of n processes requires at least n+1 processes.
Sysop: | Keyop |
---|---|
Location: | Huddersfield, West Yorkshire, UK |
Users: | 292 |
Nodes: | 16 (2 / 14) |
Uptime: | 193:02:12 |
Calls: | 6,616 |
Files: | 12,166 |
Messages: | 5,315,306 |