首页 > 代码库 > 进程篇(3: 基本进程控制)--请参照本博客“操作系统”专栏

进程篇(3: 基本进程控制)--请参照本博客“操作系统”专栏

1. 进程标识符:

每个进程都有一个非负整型表示的唯一进程ID。但进程ID可以重用,当一个进程终止之后,其进程ID就可以再次被重用了。

UNIX系统中常常有一些专用的进程:

  • ID为0的进程通常是调度进程,常常被称为交换进程(swapper),该进程是内核的一部分,它并不执行磁盘上的任何程序,因此也被称为系统进程。
  • ID为1的进程通常是init进程,在自举过程结束后由内核调用,在比较新的版本中是/sbin/init。此进程负责在自举内核后启动一个UNIX系统。init通常读取与系统有关的初始化文件,并将系统引导到一个状态!
  • ID为2的进程是页守护进程,此进程负责支持虚拟存储系统的分页操作。

Unix中返回一些与进程相关的标识符的函数:

1) 返回调用进程及其父进程的ID:

 1 SYNOPSIS
 2        #include <sys/types.h>
 3        #include <unistd.h>
 4 
 5        pid_t getpid(void);
 6        pid_t getppid(void);
 7 
 8 DESCRIPTION
 9        getpid() returns the process ID of the calling process.  (This is often used by routines that generate
10        unique temporary filenames.)
11 
12        getppid() returns the process ID of the parent of the calling process.
13 
14 ERRORS
15        These functions are always successful.

下面我们来编写一段程序返回一个程序的调用进程的id号:

 1 #include <stdio.h>
 2 #include <sys/types.h>
 3 #include <unistd.h>
 4 
 5 int main(void)
 6 {
 7     pid_t calling_id = getpid();
 8     printf("The calling process id: %d\n",calling_id);
 9     return 0;
10 }

我在我的计算机上运行./a.out 三次,结果如下:

1 The calling process id: 5539
2 The calling process id: 5542
3 The calling process id: 5543

可见getpid返回的是调用 pid_t calling_id = getpid(); 这段代码的进程的进程id号。因为每次调用都不会重用上一次的进程id号,所以呈现递增的趋势!

 

下面我们编一段代码返回调用进程的父进程id号:

 1 #include <stdio.h>
 2 #include <sys/types.h>
 3 #include <unistd.h>
 4 
 5 int main(void)
 6 {
 7     pid_t pp_id = getppid();
 8     printf("The parent id of calling process: %d\n",pp_id);
 9     return 0;
10 }

我们在shell中连续运行3次./a.out,结果如下:

The parent id of calling process: 5088
The parent id of calling process: 5088
The parent id of calling process: 5088

我们用ps命令查看当前用户系统中运行的程序id:

  PID TTY          TIME CMD
 2651 pts/0    00:00:00 bash
 5088 pts/0    00:00:00 bash
 5670 pts/0    00:00:00 ps

显然这个pp_id就是当前运行a.outd的shell进程的id。

 

2)返回调用程序的用户及有效用户id:

 1 SYNOPSIS
 2        #include <unistd.h>
 3        #include <sys/types.h>
 4 
 5        uid_t getuid(void);
 6        uid_t geteuid(void);
 7 
 8 DESCRIPTION
 9        getuid() returns the real user ID of the calling process.
10 
11        geteuid() returns the effective user ID of the calling process.
12 
13 ERRORS
14        These functions are always successful.

3) 返回进程的组id和有效组id:

 1 SYNOPSIS
 2        #include <unistd.h>
 3        #include <sys/types.h>
 4 
 5        gid_t getgid(void);
 6        gid_t getegid(void);
 7 
 8 DESCRIPTION
 9        getgid() returns the real group ID of the calling process.
10 
11        getegid() returns the effective group ID of the calling process.
12 
13 ERRORS
14        These functions are always successful.

 

2. 进程的创建,执行和结束终止:

  2.1  进程创建:

    2.1.1 fork 函数:

NAME
       fork - create a child process(创建一个子进程)

SYNOPSIS
       #include <unistd.h>

       pid_t fork(void);

DESCRIPTION
       fork()  creates a new process by duplicating the calling process(复制调用进程来创建新进程).  The new process, referred
       to as the child, is an exact duplicate of the calling process, referred to  as  the  parent,
       except for the following points:

       *  The child has its own unique process ID, and this PID does not match the ID of any exist‐
          ing process group (setpgid(2)).

       *  The childs parent process ID is the same as the parents process ID.

       *  The child does not inherit its parents memory locks (mlock(2), mlockall(2)).(子进程并不继承父进程的内存锁)

       *  Process resource utilizations (getrusage(2)) and CPU time counters (times(2))  are  reset
          to zero in the child.(子进程的资源使用计数和CPU时间计数都被值成0*  The childs set of pending signals is initially empty (sigpending(2)).(子进程的挂起信号量的数目初始化为0)

       *  The child does not inherit semaphore adjustments from its parent (semop(2)).(子进程并不继承父进程的信号量调节器)

       *  The child does not inherit record locks from its parent (fcntl(2)).(子进程并不继承父进程的记录锁)

       *  The  child  does  not  inherit timers from its parent (setitimer(2), alarm(2), timer_cre‐
          ate(2)).(子进程不从父进程中继承时间计数器)

       *  The child does not inherit  outstanding  asynchronous  I/O  operations  from  its  parent
          (aio_read(3),  aio_write(3)),  nor does it inherit any asynchronous I/O contexts from its
          parent (see io_setup(2)). (子进程并不继承父进程的异步I/O操作和异步I/O内容)

       The process attributes in the preceding list are all specified in POSIX.1-2001.  The  parent
       and child also differ with respect to the following Linux-specific process attributes:

       *  The  child does not inherit directory change notifications (dnotify) from its parent (see
          the description of F_NOTIFY in fcntl(2)).

       *  The prctl(2) PR_SET_PDEATHSIG setting is reset so that the child does not receive a  sig‐
          nal when its parent terminates.

       *  The  default timer slack value is set to the parents current timer slack value.  See the
          description of PR_SET_TIMERSLACK in prctl(2).

       *  Memory mappings that have been marked with the  madvise(2)  MADV_DONTFORK  flag  are  not
          inherited across a fork().

       *  The termination signal of the child is always SIGCHLD (see clone(2)).

       *  The  port  access  permission  bits  set by ioperm(2) are not inherited by the child; the
          child must turn on any bits that it requires using ioperm(2).

       Note the following further points:

       *  The child process is created with a single thread—the one that called fork().  The entire
          virtual  address  space of the parent is replicated in the child, including the states of
          mutexes, condition variables, and other pthreads objects; the  use  of  pthread_atfork(3)
          may be helpful for dealing with problems that this can cause.

       *  The  child  inherits  copies  of  the  parents  set of open file descriptors.  Each file
          descriptor in the child refers to the same open file description  (see  open(2))  as  the
          corresponding  file  descriptor in the parent.  This means that the two descriptors share
          open file status flags, current file offset, and signal-driven I/O  attributes  (see  the
          description of F_SETOWN and F_SETSIG in fcntl(2)).

       *  The  child  inherits  copies  of  the parents set of open message queue descriptors (see
          mq_overview(7)).  Each descriptor in the child refers to  the  same  open  message  queue
          description  as  the  corresponding  descriptor  in  the parent.  This means that the two
          descriptors share the same flags (mq_flags).

       *  The child inherits copies of the parents set of open directory streams (see opendir(3)).
          POSIX.1-2001  says  that  the corresponding directory streams in the parent and child may
          share the directory stream positioning; on Linux/glibc they do not.

RETURN VALUE
       On success, the PID of the child process is returned in the parent, and 0 is returned in the
       child.   On failure, -1 is returned in the parent, no child process is created, and errno is
       set appropriately.(返回值)

ERRORS
       EAGAIN fork() cannot allocate sufficient memory to copy the parents page tables  and  allo‐
              cate a task structure for the child.

       EAGAIN It  was  not  possible  to  create  a  new  process because the callers RLIMIT_NPROC
              resource limit was encountered.  To exceed this limit, the process must  have  either
              the CAP_SYS_ADMIN or the CAP_SYS_RESOURCE capability.

       ENOMEM fork() failed to allocate the necessary kernel structures because memory is tight.

       ENOSYS fork() is not supported on this platform (for example, hardware without a Memory-Man‐
              agement Unit).

CONFORMING TO
       SVr4, 4.3BSD, POSIX.1-2001.

NOTES
       Under Linux, fork() is implemented using copy-on-write pages, so the only  penalty  that  it
       incurs  is the time and memory required to duplicate the parents page tables, and to create
       a unique task structure for the child.

       Since version 2.3.3, rather than invoking the kernels fork() system call, the glibc  fork()
       wrapper  that is provided as part of the NPTL threading implementation invokes clone(2) with
       flags that provide the same effect as the traditional system call.  (A  call  to  fork()  is
       equivalent  to  a  call  to  clone(2)  specifying flags as just SIGCHLD.)  The glibc wrapper
       invokes any fork handlers that have been established using pthread_atfork(3).

      下面一段程序演示了fork函数,可以看出,子进程对变量所做的改变并不影响父进程中变量的值!

 1 #include "apue.h"
 2 
 3 int glob = 6; /* external variable in initialized data */
 4 char buf[] = "a write to stdout\n";
 5 
 6 int
 7 main(void)
 8 {
 9     int var; /* automatic variable on the stack */
10     pid_t pid;
11 
12     var = 88;
13     if(write(STDOUT_FILENO,buf,sizeof(buf)-1) != sizeof(buf)-1)
14         err_sys("write error");
15     printf("before fork\n");
16 
17     if((pid = fork()) < 0)
18     {
19         err_sys("fork error");
20     }
21     else if(pid == 0) /* child! */
22     {
23         glob++;
24         var++;
25     }
26     else
27         sleep(2);
28 
29     printf("pid = %d, glob = %d, var = %d\n",getpid(),glob,var);
30     exit(0);
31 }

$ ./a.out > result

 

a write to stdout
before fork
pid = 6275, glob = 7, var = 89
before fork
pid = 6274, glob = 6, var = 88

一般来说,在调用fork函数之后,父进程和子进程的执行顺序是不确定的,这取决于内核所使用的进程调度算法!

如果要实现父子进程间的的同步,必须实现进程间的通信,这里,让父进程sleep两秒而让子进程先执行!