您正在查看: 服务器 分类下的文章

chapter 12


Chapter 12

12.1

This is not a multithreading problem, as one might first guess. The standard I/O routines are indeed thread-safe. When we call fork, each process gets a copy of the standard I/O data structures. When we run the program with standard output attached to a terminal, the output is line buffered, so every time we print a line, the standard I/O library writes it to our terminal. However, if we redirect the standard output to a file, then the standard output is fully buffered. The output is written when the buffer fills or the process closes the stream. When we fork in this example, the buffer contains several printed lines not yet written, so when the parent and the child finally flush their copies of the buffer, the initial duplicate contents are written to the file.

12.3

Theoretically, if we arrange for all signals to be blocked when the signal handler runs, we should be able to make a function async-signal-safe. The problem is that we don't know whether any of the functions we call might unmask a signal that we've blocked, thereby making it possible for the function to be reentered through another signal handler.

12.4

On FreeBSD 5.2.1, we get a continuous spray of error messages, and after a while, the program drops core. With gdb, we are able to see that the program was stuck in an infinite loop during initialization. The program initialization calls thread initialization functions, which call malloc. The malloc function, in turn, calls getenv to find the value of the MALLOC_OPTIONS environment variable. Our implementation of getenv calls pthread functions, which then try to call the thread initialization functions. Eventually, we hit an error and get stuck in a similar infinite loop after abort is called. After half a million or so stack frames, the process exits, generating a core dump.

12.5

We still need fork if we want to run a program from within another program (i.e., before calling exec).

12.6

Figure C.12 shows a thread-safe sleep implementation that uses select to delay for the specified amount of time. It is thread-safe because it doesn't use any unprotected global or static data and calls only other thread-safe functions.

Figure C.12. A thread-safe implementation of sleep

#include <unistd.h>
#include <time.h>
#include <sys/select.h>

unsigned
sleep(unsigned nsec)
{
int n;
unsigned slept;
time_t start, end;
struct timeval tv;

tv.tv_sec = nsec;
tv.tv_usec = 0;
time(&start);
n = select(0, NULL, NULL, NULL, &tv);
if (n == 0)
return(0);
time(&end);
slept = end - start;
if (slept >= nsec)
return(0);
return(nsec - slept);
}

12.7

The implementation of a condition variable most likely uses a mutex to protect its internal structure. Because this is an implementation detail and therefore hidden, there is no portable way for us to acquire and release the lock in the fork handlers. Since we can't determine the state of the internal lock in a condition variable after calling fork, it is unsafe for us to use the condition variable in the child process.

chapter 13


Chapter 13

13.1

If it calls chroot, the process will not be able to open /dev/log. The solution is for the daemon to call openlog with an option of LOG_NDELAY, before calling chroot. This opens the special device file (the UNIX domain datagram socket), yielding a descriptor that is still valid, even after a call to chroot. This scenario is encountered in daemons, such as ftpd (the File Transfer Protocol daemon), that specifically call chroot for security reasons but still need to call syslog to log error conditions.

13.3

Figure C.13 shows a solution. The results depend on the platform. Recall that daemonize closes all open file descriptors and then reopens the first three to /dev/null. This means that the process won't have a controlling terminal, so getlogin won't be able to look in the utmp file for the process's login entry. Thus, on linux 2.4.22 and Solaris 9, we find that a daemon has no login name.

Figure C.13. Call daemonize and then obtain login name

#include "apue.h"

int
main(void)
{

FILE *fp;
char *p;

daemonize("getlog");
p = getlogin();
fp = fopen("/tmp/getlog.out", "w");
if (fp != NULL) {
if (p == NULL)
fprintf(fp, "no login name\n");
else
fprintf(fp, "login name: %s\n", p);
}
exit(0);
}


Under FreeBSD 5.2.1 and Mac OS X 10.3, however, the login name is maintained in the process table and copied across a fork. This means that the process can always get the login name, unless the parent didn't have one to start out (such as init when the system is bootstrapped).

chapter 7


Chapter 7

7.1

It appears that the return value from printf (the number of characters output) becomes the return value of main. Not all systems exhibit this property.

7.2

When the program is run interactively, standard output is usually line buffered, so the actual output occurs when each newline is output. If standard output were directed to a file, however, it would probably be fully buffered, and the actual output wouldn't occur until the standard I/O cleanup is performed.

7.3

On most UNIX systems, there is no way to do this. Copies of argc and argv are not kept in global variables like environ is.

7.4

This provides a way to terminate the process when it tries to dereference a null pointer, a common C programming error.

7.5

The definitions are

typedef void  Exitfunc(void);

int atexit(Exitfunc *func);

7.6

calloc initializes the memory that it allocates to all zero bits. ISO C does not guarantee that this is the same as either a floating-point 0 or a null pointer.

7.7

The heap and the stack aren't allocated until a program is executed by one of the exec functions (described in Section 8.10).

7.8

The executable file (a.out) contains symbol table information that can be helpful in debugging a core file. To remove this information, the strip(1) command is used. Stripping the two a.out files reduces their size to 381,976 and 2,912 bytes.

7.9

When shared libraries are not used, a large portion of the executable file is occupied by the standard I/O library.

7.10

The code is incorrect, since it references the automatic integer val through a pointer after the automatic variable is no longer in existence. Automatic variables declared after the left brace that starts a compound statement disappear after the matching right brace.

chapter 8


Chapter 8

8.1

To simulate the behavior of the child closing the standard output when it exits, add the following line before calling exit in the child:

fclose(stdout);

To see the effects of doing this, replace the call to printf with the lines

i = printf("pid = %d, glob = %d, var = %d\n",
 getpid(), glob, var);
sprintf(buf, "%d\n", i);
write(STDOUT_FILENO, buf, strlen(buf));

You need to define the variables i and buf also.

This assumes that the standard I/O stream stdout is closed when the child calls exit, not the file descriptor STDOUT_FILENO. Some versions of the standard I/O library close the file descriptor associated with standard output, which would cause the write to standard output to also fail. In this case, dup standard output to another descriptor, and use this new descriptor for the write.

8.2

Consider Figure C.7.

Figure C.7. Incorrect use of vfork

#include "apue.h"

static void f1(void), f2(void);

int
main(void)
{
f1();
f2();
_exit(0);
}

static void
f1(void)
{
pid_tpid;

if ((pid = vfork()) < 0)
err_sys("vfork error");
/* child and parent both return */
}

static void
f2(void)
{
char buf[1000]; /* automatic variables */
int i;

for (i = 0; i < sizeof(buf); i++)
buf[i] = 0;
}


When vfork is called, the parent's stack pointer points to the stack frame for the f1 function that calls vfork. Figure C.8 shows this.

Figure C.8. Stack frames when vfork is called


vfork causes the child to execute first, and the child returns from f1. The child then calls f2, and its stack frame overwrites the previous stack frame for f1. The child then zeros out the automatic variable buf, setting 1,000 bytes of the stack frame to 0. The child returns from f2 and then calls _exit, but the contents of the stack beneath the stack frame for main have been changed. The parent then resumes after the call to vfork and does a return from f1. The return information is often stored in the stack frame, and that information has probably been modified by the child. After the parent resumes, what happens with this example depends on many implementation features of your UNIX system (where in the stack frame the return information is stored, what information in the stack frame is wiped out when the automatic variables are modified, and so on). The normal result is a core file, but your results may differ.

8.3

In Figure 8.13, we have the parent output first. When the parent is done, the child writes its output, but we let the parent terminate. Whether the parent terminates or whether the child finishes its output first depends on the kernel's scheduling of the two processes (another race condition). When the parent terminates, the shell starts up the next program, and this next program can interfere with the output from the previous child.

We can prevent this from happening by not letting the parent terminate until the child has also finished its output. Replace the code following the fork with the following:

  else if (pid == 0) {
WAIT_PARENT(); /* parent goes first */
charatatime("output from child\n");
TELL_PARENT(getppid()); /* tell parent we're done */
  } else {
charatatime("output from parent\n");
TELL_CHILD(pid);  /* tell child we're done */
WAIT_CHILD();  /* wait for child to finish */
  }

We won't see this happen if we let the child go first, since the shell doesn't start the next program until the parent terminates.

8.4

The same value (/home/sar/bin/testinterp) is printed for argv[2]. The reason is that execlp ends up calling execve with the same pathname as when we call execl directly. Recall Figure 8.15.

8.5

A function is not provided to return the saved set-user-ID. Instead, we must save the effective user ID when the process is started.

8.6

The program in Figure C.9 creates a zombie.

Figure C.9. Create a zombie and look at its status with ps

#include "apue.h"

#ifdef SOLARIS
#define PSCMD "ps -a -o pid,ppid,s,tty,comm"
#else
#define PSCMD "ps -o pid,ppid,state,tty,command"
#endif

int
main(void)
{
pid_t pid;

if ((pid = fork()) < 0)
err_sys("fork error");
else if (pid == 0)/* child */
exit(0);

/* parent */
sleep(4);
system(PSCMD);

exit(0);
}


Zombies are usually designated by ps(1) with a status of Z:

  $ ./a.out
  PID  PPID S TTCOMMAND
 3395  3264 S pts/3bash
29520 3395  S pts/3./a.out
29521 29520 Z pts/3[a.out] <defunct>
29522 29520 R pts/3ps -o pid,ppid,state,tty,command

chapter 9


Chapter 9

9.1

The init process learns when a terminal user logs out, because init is the parent of the login shell and receives the SIGCHLD signal when the login shell terminates.

For a network login, however, init is not involved. Instead, the login entries in the utmp and wtmp files, and their corresponding logout entries, are usually written by the process that handles the login and detects the logout (telnetd in our example).