• 1. 进程
  • 2. 进程
  • 3. 进程控制的相关函数进程的创建 派生进程:fork(),vfork(); 创建执行其他程序的进程:exec();(选学) Linux系统特有的调用:_ _clone(); (选学) 进程的等待 wait(),waitpid(); 进程的终止 exit(),atexit(),on_exit(),abort(),_exit(); 其他函数 system() (选学) ,getpid()。
  • 4. 进程的创建fork()函数 #include #include pid_t fork(void); fork在英文中是“分叉”的意思。一个进程在运行中,如果使用了fork ,就产生了另一个进程,于是进程就“分叉”了。当前进程为父进程,通过fork()会产生一个子进程。对于父进程,返回子程序的进程号,而对于子程序则返回0,这就是fork函数的特点 “调用一次,返回两次”,出错则返回-1。 fork函数是Unix系统最杰出的成就之一。
  • 5. 进程的创建fork函数实例以及说明: 因父进程和子进程的运行无关,父进程或子进程返回的顺序随机的,因此运行结果不唯一。 vfork()函数以及实例 vfork创建新进程的主要目的在于用exec函数执行另外的程序,实际上,在没调用exec或exit之前子进程的运行中是与父进程共享数据段的。在vfork调用中,子进程先运行,父进程挂起,直到子进程调用exec或exit,在这以后,父子进程的执行顺序不再有限制。
  • 6. 进程的创建fork函数实例以及说明: 因父进程和子进程的运行无关,父进程或子进程返回的顺序随机的,因此运行结果不唯一。 vfork()函数以及实例 vfork创建新进程的主要目的在于用exec函数执行另外的程序,实际上,在没调用exec或exit之前子进程的运行中是与父进程共享数据段的。在vfork调用中,子进程先运行,父进程挂起,直到子进程调用exec或exit,在这以后,父子进程的执行顺序不再有限制。
  • 7. 进程的创建fork运行结果vfork运行结果
  • 8. 进程的创建fork运行结果vfork运行结果
  • 9. 进程的创建(选学)exec()函数族 用fork创建子进程后执行的是和父进程相同的程序(但有可能执行不同的代码分支),子进程往往要调用一种exec函数以执行另一个程序。当进程调用一种exec函数时,该进程的用户空间代码和数据完全被新程序替换,从新程序的启动例程开始执行。调用exec并不创建新进程,所以调用exec前后该进程的id并未改变。
  • 10. 进程的创建(选学)其实有六种以exec开头的函数,统称exec函数: #include int execl(const char *path, const char *arg, ...); int execlp(const char *file, const char *arg, ...); int execle(const char *path, const char *arg, ..., char *const envp[]); int execv(const char *path, char *const argv[]); int execvp(const char *file, char *const argv[]); int execve(const char *path, char *const argv[], char *const envp[]);
  • 11. 进程的创建(选学)这些函数如果调用成功则加载新的程序从启动代码开始执行,不再返回,如果调用出错则返回-1,所以exec函数只有出错的返回值而没有成功的返回值。 在exec函数族中,后缀l、v、p、e添加到exec后,所指定的函数将具有某种操作能力有后缀: p(path)时,函数可以利用DOS的PATH变量查找子程序文件。 假如你希望执行命令 /bin/cat /etc/passwd /etc/group(通过cat命令查看/etc/passwd和/etc/group的内容)
  • 12. 进程的创建(选学)l(list)时,希望接收以逗号分隔的参数列表,列表以NULL指针作为结束标志。 execl( "/bin/cat", "/etc/passed", "/etc/group", NULL); v(vector)时,希望接收到一个以NULL结尾的字符串数组的指针。 char* argv[] = {"/bin/cat", "/etc/passed", "/etc/group", NULL} execv( "/bin/cat", argv ); e(environment)时,函数传递参数envp,允许改变子进程的环境,无后缀e时,子进程使用当前程序的环境。envp也是一个以NULL结尾的字符串数组指针。
  • 13. 进程的创建(选学)execl和execlp完全相同,execv和execvp完全相同。 execl()和execv()要求提供可执行文件的绝对或相对路径名,而execlp()和execvp()使用$PATH环境变量查找path。
  • 14. 进程的创建(选学)一个完整的例子 #include #include int main(void) { execlp("ps", "ps", "-o", "pid,ppid,pgrp,session,tpgid,comm", NULL); perror("exec ps"); exit(1); } [root@localhost ch06]# ./execlp PID PPID PGRP SESS TPGID COMMAND 9125 9122 9125 9125 9937 bash 9937 9125 9937 9125 9937 ps
  • 15. 进程的创建(选学)由于exec函数只有错误返回值,只要返回了一定是出错了,所以不需要判断它的返回值,直接在后面调用perror即可。 注意在调用execlp时传了两个"ps"参数,第一个"ps"是程序名,execlp函数要在PATH环境变量中找到这个程序并执行它,而第二个"ps"是第一个命令行参数,execlp函数并不关心它的值,只是简单地把它传给ps程序,ps程序可以通过main函数的argv[0]取到这个参数。 调用exec后,原来打开的文件描述符仍然是打开的。利用这一点可以实现I/O重定向。 实例execve.c
  • 16. 进程的创建(选学)Linux特有的调用——clone clone是Linux2.0以后才具备的新功能,它较fork更强(可认为fork是clone要实现的一部分),可以使得创建的子进程共享父进程的资源,并且要使用此函数必须在编译内核时设置clone_actually_works_ok选项。 clone函数的原型为: #inlude int clone(int (*fn)(void *), void *child_stack, int flags, void *arg); 此函数返回创建进程的PID,函数中的flags标志用于设置创建子进程时的相关选项,具体含义如下表:
  • 17. 进程的创建(选学)标志含义CLONE_PARENT创建的子进程的父进程是调用者的父进程,新进程与创建它的进程成了“兄弟”而不是“父子”CLONE_FS子进程与父进程共享相同的文件系统,包括root、当前目录、umaskCLONE_FILES子进程与父进程共享相同的文件描述符(file descriptor)表CLONE_NEWNS在新的namespace启动子进程,namespace描述了进程的文件hierarchyCLONE_SIGHAND子进程与父进程共享相同的信号处理(signal handler)表CLONE_PTRACE若父进程被trace,子进程也被traceCLONE_VFORK父进程被挂起,直至子进程释放虚拟内存资源CLONE_VM子进程与父进程运行于相同的内存空间CLONE_PID子进程在创建时PID与父进程一致CLONE_THREADLinux 2.4中增加以支持POSIX线程标准,子进程与父进程共享相同的线程群
  • 18. 进程等待在多进程处理时,用户可能需要用到有关进程等待的操作。这种等待可以是进程组成员间的等待,也可以是父进程对子进程的等待。 例如,当一个进程结束时,Linux系统将产生一个SIGCHLD信号通知其父进程。在父进程未查询子进程结束的原因时,该子进程虽然停止了,但并未完全结束。此时该子进程被称为僵尸进程(zombie process)这时的处理方法之一就是使用进程等待的系统调用 wait 和waitpid。
  • 19. 进程等待wait()函数 #include #include pid_t wait(int *status); 进程一旦调用了wait,就立即阻塞自己,由wait()函数分析是否当前的某个子进程已经退出,如果让它找到了这样一个已经变成僵尸的子进程,wait就会收集这个子进程的信息,并把它彻底销毁后返回;如果没有找到这样一个子进程,wait就会一直阻塞在这里,直到有一个出现为止。可认为wait()系统调用的作用就是负责回收僵尸进程 。
  • 20. 进程等待因为在下列两个事件都已经发生的情况下进程才会完全终止: (1)进程自己已经退出(或已经被一个信号杀死) (2)它的父进程已经执行了WAIT系统调用以观察发生了什么。如果已经退出或被杀死而它的父进程还没有为它执行WAIT的进程将进入某种挂起状态,有时被称为僵死状态(Zombie State),这种进程不再参与调度,它的报警时钟被关闭,但它仍将留在进程表中,它的内存被释放。僵死是一种临时状态,很少会持续较长的时间,当父进程最后执行WAIT时,将释放进程表项,并通知文件系统和内核。
  • 21. 进程等待对于参数status是一个指向int型的变量,用来保存子进程退出时的状态(例如子进程中有exit(2009)或return(2009),那么这个变量里的某些二进制位存放的就是2009,也就是子进程的返回值),但如果我们对这个子进程是如何死掉的毫不在意,只想把这个僵尸进程消灭掉,(事实上绝大多数情况下,我们都会这样想),我们就可以设定这个参数为NULL,就象下面这样: pid = wait(NULL); 如果成功,wait会返回被收集的子进程的进程ID,如果调用进程没有子进程,调用就会失败,此时wait返回-1,同时errno被置为ECHILD。
  • 22. 进程等待Wati()函数实例:wait1.c 编译并运行: $ gcc wait1.c -o wait1 $ ./wait1 This is child process with pid of 1508 I catched a child process with pid of 1508 可以明显注意到,在第2行结果打印出来前有10 秒钟的等待时间,这就是我们设定的让子进程睡眠的时间,只有子进程从睡眠中苏醒过来,它才能正常退出,也就才能被父进程捕捉到。其实这里我们不管设定子进程睡眠的时间有多长,父进程都会一直等待下去。
  • 23. 进程等待如果参数status的值不是NULL,wait就会把子进程退出时的状态取出并存入其中,这是一个整数值,指出了子进程是正常退出还是被非正常结束的(一个进程也可以被其他进程用信号结束),以及正常结束时的返回值,或被哪一个信号结束的等信息。由于这些信息被存放在一个整数的不同二进制位中,所以用常规的方法读取会非常麻烦,人们就设计了一套专门的宏(macro)来完成这项工作,下面我们来学习一下其中最常用的两个:
  • 24. 进程等待1、WIFEXITED(status) 这个宏用来指出子进程是否为正常退出的,如果是,它会返回一个非零值。 请注意,虽然名字一样,这里的参数status并不同于wait唯一的参数--指向整数的指针status,而是那个指针所指向的整数,切记不要搞混了。 2、WEXITSTATUS(status) 当WIFEXITED返回非零值时,我们可以用这个宏来提取子进程的返回值,如果子进程调用exit(5)退出,WEXITSTATUS(status) 就会返回5;如果子进程调用exit(7),WEXITSTATUS(status)就会返回7。请注意,如果进程不是正常退出的,也就是说, WIFEXITED返回0,这个值就毫无意义
  • 25. 进程等待wait2.c实例。 编译并运行: [root@localhost ch06]# gcc -o wait2 wait2.c [root@localhost ch06]# ./wait2 This is child process with pid of 12668. the child process 12668 exit normally. the return code is 3. 父进程准确捕捉到了子进程的返回值3,并把它打印了出来。
  • 26. 进程等待waitpid()函数 waitpid() 系统调用在Linux函数库中的原型是: #include /#include pid_t waitpid(pid_t pid,int *status,int options); 从本质上讲,系统调用waitpid和wait的作用是完全相同的,但waitpid多出了两个可由用户控制的参数pid和options,从而为我们编程提供了另一种更灵活的方式。 下面我们就来详细介绍一下这两个参数:
  • 27. 进程等待pid参数 pid>0时,只等待进程ID等于pid的子进程,不管其它已经有多少子进程运行结束退出了,只要指定的子进程还没有结束,waitpid就会一直等下去。 pid=-1时,等待任何一个子进程退出,没有任何限制,此时waitpid和wait的作用一模一样。 pid=0时,等待同一个进程组中的任何子进程,如果子进程已经加入了别的进程组,waitpid不会对它做任何理睬。 pid<-1时,等待一个指定进程组中的任何子进程,这个进程组的ID等于pid的绝对值。
  • 28. 进程等待options参数 options提供了一些额外的选项来控制waitpid,目前在Linux中只支持WNOHANG和WUNTRACED两个选项,这是两个常数,可以用"|"运算符把它们连接起来使用,比如: ret=waitpid(-1,NULL,WNOHANG | WUNTRACED); 如果我们不想使用它们,也可以把options设为0,如: ret=waitpid(-1,NULL,0); 如果使用了WNOHANG参数调用waitpid,即使没有子进程退出,它也会立即返回,不会像wait那样永远等下去。 而WUNTRACED参数,由于涉及到一些跟踪调试方面的知识,加之极少用到,这里就不多讲。
  • 29. 进程等待Waitpid()函数实例 编译并运行: $ gcc waitpid.c -o waitpid $ ./waitpid No child exited No child exited No child exited No child exited No child exited No child exited No child exited No child exited No child exited No child exited successfully get child 1526父进程经过10次失败的尝试之后,终于收集到了退出的子进程。
  • 30. 进程等待父进程调用wait或waitpid时可能会: 阻塞(如果它的所有子进程都还在运行)。 带子进程的终止信息立即返回(如果一个子进程已终止,正等待父进程读取其终止信息)。 出错立即返回(如果它没有任何子进程)。 这两个函数的区别是: 如果父进程的所有子进程都还在运行,调用wait将使父进程阻塞,而调用waitpid时如果在options参数中指定WNOHANG可以使父进程不阻塞而立即返回0。 wait等待第一个终止的子进程,而waitpid可以通过pid参数指定等待哪一个子进程。
  • 31. 进程的终止进程结束可通过相应的函数实现: void exit(int status); void _exit(int status); 终止正在运行的程序,关闭所有被该文件打开的文件描述符 _exit与exit不同的是可以关闭一些Linux下特有的退出句柄。 int atexit(void (*function)(void)); 用于注册一个不带参数也没有返回值的函数以供程序正常退出时被调用。参数function是指向所调用程序的文件指针。调用成功返回0,否则返回-1,并将errno设置为相应值
  • 32. 进程的终止int on_exit(void (*function)(int,void *),void *arg); 作用与atexit类似,不同是其注册的函数具有参数,退出状态和参数arg都是传递给该函数使用。 void abort(void); 用来发送一个SIGABRT信号,该信号将使当前进程终止。 void assert( int expression ); 先计算表达式 expression ,如果其值为0,那么它先向stderr打印一条出错信息,然后通过调用 abort 来终止程序运行。常用来检测某些参数是否有不当情况出现,并在不当情况发生时以结束进程作为相应处理。
  • 33. 进程的终止atexit实例 assert实例
  • 34. system函数(选学)system函数是一个和操作系统紧密相关的函数。用户可以使用它在自己的程序中调用系统提供的各种命令。 system函数的说明如下: #include int system(const char *cmdstring); 参数cmdstring是一个字符串指针。 如果cmd是一个空指针,则仅仅当命令处理程序可用时,system返回非0值。
  • 35. system函数(选学)因为system在其实现中调用了fork,exec和waitpid,因此有三种返回值: 1)如果fork失败或者waitpid返回除EINTR之外的出错,则system返回-1,而且errno中设置了错误类型 2). 如果exec失败(表示不能执行shell),则其返回值如同shell执行了exit(127)一样。 3). 否则所有三个函数(fork,exec和waitpid)都执行成功,并且system的返回值是shell的终止状态,其格式已经在waitpid中说明。
  • 36. system函数(选学)如果一个进程正以特殊的权限(设置用户ID和设置组ID)运行,它又想生成另一个进程执行另一个程序,则它应当直接使用fork和exec,而且在fork之后,exec之前要更改回普通权限。设置用户ID和设置组ID程序绝不应调用system函数,因为特殊权限会在system中执行了fork和exec后扔保持下来。 system例子mainnew.c
  • 37. 一个简单的例子(生产者-消费者问题)int buffer[N]; int count = 0; int in = 0, out = 0; pthread_cond_t full = PTHREAD_COND_INITIALIZER; pthread_cond_t empty = PTHREAD_COND_INITIALIZER; pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER;
  • 38. 一个简单的例子(生产者-消费者问题)void put(int o) { pthread_mutex_lock(&lock); while (count == N) pthread_cond_wait(&empty, &lock); buffer[in] = o; in = (in + 1) % N; count++; pthread_mutex_unlock(&lock); pthread_mutex_signal(&full); }
  • 39. 一个简单的例子(生产者-消费者问题)int get() { pthread_mutex_lock(&lock); while (count == 0) pthread_cond_wait(&full, &lock); int o = buffer[out]; out = (out + 1) % N; count--; pthread_mutex_unlock(&lock); pthread_mutex_signal(&empty); return o; }