当前位置:   article > 正文

UNIX环境高级编程-进程控制_fexecve

fexecve

目录

相关函数列表

fork函数

一个fork和waitpid的例子

exec系列函数

解释器文件和System

进程会计

进程优先级和CPU时间

参考


 

 

相关函数列表

  1. //下列函数返回一个进程的标识符  
  2. #include <unistd.h>  
  3. pid_t getpid(void);  
  4. pid_t getppid(void);  
  5. uid_t getuid(void);  
  6. uid_t geteuid(void);  
  7. gid_t getgid(void);  
  8. gid_t getegid(void);  
  9.   
  10. //创建新进程  
  11. #include <unistd.h>  
  12. pid_t fork(void);  
  13. pid_t vfork(void);  
  14.   
  15.   
  16. //当一个进程正常或异常终止时,内核就向其父进程发送SIGCHLD信号,wait或waitpid的进程会  
  17. //1.如果其所有子进程都还在运行则阻塞  
  18. //2.如果一个子进程已停止,正等待父进程获取其终止状态,则取得该子进程的终止状态立即返回  
  19. //3.如果它没有任何子进程,则立即出错返回  
  20. //两个函数的区别  
  21. //1.在一个子进程终止前,wait使其调用者阻塞,而waitpid有一选项,可使调用者不阻塞  
  22. //2.waitpid并不等待在其调用之后的第一个终止子进程,它有若干个选项,可以控制它所等待的进程  
  23. #include <sys/wait.h>  
  24. pid_t wait(int *statloc);  
  25. pid_t waitpid(pid_t pid, int *statloc, int options);  
  26.   
  27. //Single UNIX Specification包括了另一个取得进程终止状态的函数--waitid,此函数类似于waitpid  
  28. #include <sys/wait.h>  
  29. int waitid(idtype_t idtype, id_t id, siginfo_t *infop, int options);  
  30.   
  31. //大多数UNIX系统实现提供了另外两个函数,这两个函数是从UNIX系统BSD分支延袭下来的  
  32. #include <sys/types.h>  
  33. #include <sys/wait.h>  
  34. #include <sys/time.h>  
  35. #include <sys/resource.h>  
  36. pid_t wait3(int *statloc, int options, struct rusage *rusage);  
  37. pid_t wait4(pid_t pid, int *statloc, int options, struct rusage *rusage)  
  38.   
  39.   
  40. //fork函数创建新进程,用exec可以初始执行新的程序,exit函数和wait函数处理终止和等待终止  
  41. //函数exec系列,一共有7个,这些函数的区别  
  42. //1.前4个函数是用路径做参数,后两个用文件名,最后一个用文件描述符  
  43. //2.与参数传递有关,(l表示list,v表示矢量vector),函数execl,execlp和execle要求将新程序的  
  44. //  每个命令参数都说明一个单独的参数。另外四个函数(execv,execvp,execve,fexecve)则应先  
  45. //  构造一个指向各参数的指针数组,然后将数组作为参数  
  46. //3.与新程序传递环境表相关,以e结尾的三个函数(execle,execve和fexecve)可以传递一个指向环境  
  47. //  字符串指针数组。其他4个函数则使用调用进程中的environ变量为新程序复制现有的环境  
  48. #include <unistd.h>  
  49. int execl(const char *pathname, const char *arg0, ... /* (char *)0 */);  
  50. int execv(const char *pathname, char *const argv[]);  
  51. int execle(const char *pathname, const char *arg0, ... /* (char *)0, char*const envp[] */);  
  52. int execve(const char *pathname, char *const argv[], char *const envp[];  
  53. int execlp(const char *filename, const char *arg0, ... /* (char *)0 */);  
  54. int execvp(const char *filename, char *const argv[]);  
  55. int fexecve(int fd, char *const argv[], char *const envp[]);  
  56.   
  57.   
  58. //设置实际用户ID和有效用户ID  
  59. #include <unistd.h>  
  60. int setuid(uid_t uid);  
  61. int setgid(gid_t gid);  
  62.   
  63. //历史上BSD支持下列函数,其功能是交换实际用户ID和有效用户ID的值  
  64. #include <unistd.h>  
  65. int setreuid(uid_t ruid, uid_t euid);  
  66. int setregid(gid_t rgid, gid_t egid);  
  67.   
  68. //POIX.1包含的两个函数类似于setuid和setgid,但只更改有效用户ID和有效组ID  
  69. #include <unistd.h>  
  70. int seteuid(uid_t uid);  
  71. int setegid(gid_t gid);  
  72.   
  73.   
  74. //下面函数用来在程序中执行一个命令字符串,因为system实际调用了fork,exec和waitpid,因此有  
  75. //三种返回值  
  76. //1.for失败或者waitpid返回除EINTR之外的出错,则system返回-1,并且设置errno以指示错误类型  
  77. //2.如果exec失败(表示不能执行shell),则其返回值如同shell执行了exit(127)一样  
  78. //3.柔则所有3个函数(fork,exec和waitpd)都成功,那么system的返回值是shell的终止状态  
  79. #include <stdlib.h>  
  80. int system(const char *cmdstring);  
  81.   
  82.   
  83. //进程会计,每个系统实现不同,但是基本结构如下  
  84. #include <sys/acct.h>  
  85. typedef u_short comp_t;  
  86. struct acct {  
  87.     char ac_flag;      //flag  
  88.     char ac_stat;      //termination status  
  89.     uid_t ac_uid;      //real user ID  
  90.     gid_t ac_gid;      //read group ID  
  91.     dev_t ac_tty;      //controlling terminal  
  92.     time_t ac_btime;   //starting calendar time  
  93.     comp_t ac_utime;   //user CPU time  
  94.     comp_t ac_stime;   //system CPU time  
  95.     comp_t ac_etime;   //elapsed time  
  96.     comp_t ac_mem;     //average memory usage  
  97.     comp_t ac_io;      //bytes transferred(read and write)  
  98.     comp_t ac_rw;      //blocks on BSD system  
  99.     char ac_comm[8];   //command name  
  100. };  
  101.   
  102.   
  103. //一个用户可能有多个登陆名,系统会记录用户登陆时的名字,下面函数可以获取  
  104. #include <unistd.h>  
  105. char *getlogin(void);  
  106.   
  107. //可以用nic函数获取和更改它的nic值,这个函数只能影响到自己的nice值,不能影响任何其他进程的  
  108. //nice值  
  109. #include <unistd.h>  
  110. int nice(int incr);  
  111.   
  112. //下面函数可以像nice一样获得进程的nice值,而且还可以获得一组相关进程的nice值  
  113. //which值可以取以下三个值之一:  
  114. //1.PRIO_PROCESS表示进程  
  115. //2.PRIO_PGRP表示进程组  
  116. //3.PRIO_USER表示用户ID  
  117. #include <sys/resource.h>  
  118. int getpriority(int which, id_t who);  
  119.   
  120. //下面函数可用于为进程,进程组和属于特定用户ID的所有进程设置优先级  
  121. #include <sys/resource.h>  
  122. int setpriority(int which, id_t who, int value);  
  123.   
  124. //任何一个进程都可以调用下面函数获得它自己的系统CPU时间,用户CPU时间,以及子进程的系统CPU  
  125. //时间,子进程的用户CPU时间  
  126. #include <sys/times.h>  
  127. clock_t times(struct tms *buf);  
  128. //tms结构体如下  
  129. struct tms {  
  130.     clock_t tms_utimes;      //user cpu time  
  131.     clock_t tms_stime;       //system cpu time  
  132.     clock_t tms_cutime;      //user cpu time,terminated children  
  133.     clock_t tms_cstime;      //system cpu time,terminated children  
  134. };  

 

 

fork函数

其创建新进程为子进程,fork函数被调用一次,但是返回两次。

子进程返回的是0

父进程返回的是子进程的进程ID

子进程和父进程继续执行fork之后的指令,子进程是父进程的副本,列如子进程获得父进程数据空间,堆和栈 

的副本(注意是子进程所拥有的副本)。父进程和子进程并不共享这些存储空间部分,父进程和子进程共享正文段。

返回子进程ID的原因是一个进程可以有多个子进程,但是没有函数可以获得所有子进程的ID,而父进程只有一个可以通过函数getppid()获得

fork之后是父进程先执行还是子进程先执行是不确定的,取决于操作系统的调度,如果要求父进程和子进程之间相互同步,则要求某种形式的进程间通信。

父进程和子进程共享相同的文件描述符

 

fork之后父进程和子进程之间对打开文件的共享


 

 

在fork之后处理文件描述符有以下两种情况

1)父进程等待子进程完成,在这种情况下,父进程无需对其描述符做任何处理。当子进程终止后,它曾进行过读,写操作的任一共享描述符的文件偏移量已做了相应的更新

2)父进程和子进程各自执行不同的程序段,这种情况下,在fork之后,父进程和子进程各自关闭他们不需要使用的文件描述符,这样就不会干扰对方使用的文件描述符,这种方法是网络服务器进程经常使用的

 

fork有以下两种用法

1)一个父进程希望复制自己,使父进程和子进程同时执行不同的代码段,这在网络服务进程中是常见的--父进程等待客户度端的服务请求,当请求到来时父进程调用fork使子进程处理此请求,父进程继续等待下一个请求

2)一个进程要执行一个不同的程序,这对shell是常见的,在这种情况下,子进程从fork返回后立即调用exec

 

fork和vfork的区别

1)子进程并不将父进程的地址空间完全复制到子进程中,因为子进程会立即调用exec,这种优提高了效率

2)vfork保证子进程优先运行,在它调用exec或exit之后父进程才可能被调度运行,当子进程调用这两个函数中的任意一个时,父进程会恢复运行。

 

 

进程有5种正常终止及3种异常终止方式

1)在main函数内执行return语句

2)调用exit函数

3)调用_exit或_Exit函数

4)进程的最后一个线程在其启动例程中执行return语句

5)进程的最后一个线程调用pthread_exit函数

 

3种异常终止具体如下

1)调用abort,产生SIGABRT信号,这是下一种异常终止的一种特列

2)当进程接收到某些信号时,信号可由进程自身(如调用abort函数),其他进程或内核产生

3)最后一个线程对"取消"(cancellation)请求作出响应

所有子进程退出后,将终止状态返回给父进程,如果父进程已经退出,则将父进程改为init进程

一个已经终止,但是其父进程尚未对其进行善后处理(获取终止子进程的有关信息,释放他仍占用的资源)的

进程被称为 僵死进程(zombie)

 

 

检查wait和waitpid锁返回的终止状态的宏

说明
 WIFEXITED(status)

若为正常终止子进程返回的状态,则为真。对于这种情况可执行WEXITSTATUS(

status),获取子进程传给exit或_exit参数的低8位

WIFSIGNALED(status)

若为异常终止进程返回的状态,则为真(接到一个不捕捉的信号),对于这种情况,

可执行WTERMSIG(status),获取使子进程终止的信号编号。另外,有些实现

(非Single UNIX Specification)定义宏WCOREDUMP(status),若已产生终止进程

的core文件,则它返回真

WIFSTOPPED(status)

若为当前暂停子进程的返回的状态,则为真。对于这种情况,可执行

WSTOPSIG(status),获取使子进程暂停的信号编号

WIFCONTINUED(status)

若在作业控制暂停后已继续的子进程返回了状态,则为真(POSIX.1的XSI扩展,

仅用于waitpid)

 

waitpid的options常量

常量说明
WCONTINUED

若是先支持作业控制,那么由id指定的任一子进程在停止后已经继续,但其状态尚未

报告,则返回其状态(POSIX.1的XSI扩展)

WNOHANG若由pid指定的子进程并不是立即可用的,则waitpid不阻塞,此时其返回值为0
WUNTRACED

若某是先支持作业控制,而由pid指定的任一子进程已处于停止状态,并且其状态自

停止以来还未报告过,则返回其状态。WIFSTOPPED宏确定返回值是否对应用与一个

停止的子进程

一个fork和waitpid的例子

  1. #include <unistd.h>
  2. #include <stdio.h>
  3. #include <stdlib.h>
  4. int global = 100;
  5. int main(int argc, char *argv[]) {
  6. int local = 9527;
  7. pid_t pid_1;
  8. pid_1 = fork();
  9. if(pid_1 > 0) { //parent
  10. waitpid(pid_1,NULL,0);
  11. printf("parent pid=%d,ppid=%d,global=%d,local=%d\n",getpid(),getppid(),global,local);
  12. }
  13. else if(pid_1 == 0) { //child
  14. global++;
  15. local++;
  16. printf("child pid=%d,ppid=%d,global=%d,local=%d\n",getpid(),getppid(),global,local);
  17. }
  18. else {
  19. }
  20. }
  21. //结果为
  22. child pid=11474,ppid=11473,global=101,local=9528
  23. parent pid=11473,ppid=11454,global=100,local=9527
  24. //strace分析程序,fork是用clone实现的,waitpid最终是调用wait4实现的
  25. ....
  26. clone(child_stack=0, flags=CLONE_CHILD_CLEARTID|CLONE_CHILD_SETTID|SIGCHLD, child_tidptr=0x7fb2b3e82a10) = 11453
  27. wait4(11453, strace: Process 11453 attached
  28. <unfinished ...>
  29. ....

一个 vfork和waitpid的例子

  1. #include <stdio.h>
  2. #include <stdlib.h>
  3. #include <unistd.h>
  4. int global = 100;
  5. int main(int argc, char *argv[]) {
  6. int local = 9527;
  7. pid_t pid = vfork();
  8. if(pid > 0) {
  9. waitpid(pid,NULL,0);
  10. printf("parent global=%d, local=%d\n", global,local);
  11. }
  12. else if(pid == 0) {
  13. global++;
  14. local++;
  15. //_exit(0);
  16. exit(0);
  17. }
  18. else {
  19. printf("error\n");
  20. }
  21. }
  22. //结果
  23. parent global=101, local=9528
  24. //通过strace分析程序
  25. ....
  26. clone(child_stack=0, flags=CLONE_CHILD_CLEARTID|CLONE_CHILD_SETTID|SIGCHLD, child_tidptr=0x7f14cf9dea10) = 11544
  27. wait4(11544, child pid=11544,ppid=11543,global=101,local=9528
  28. NULL, 0, NULL) = 11544
  29. --- SIGCHLD {si_signo=SIGCHLD, si_code=CLD_EXITED, si_pid=11544, si_uid=0, si_status=49, si_utime=0, si_stime=0} ---
  30. getppid() = 11541
  31. getpid() = 11543
  32. fstat(1, {st_mode=S_IFCHR|0620, st_rdev=makedev(136, 3), ...}) = 0
  33. ....

 

 

 

exec系列函数

竞争条件

当多个进程都企图对共享数据进行某种处理,而最后的结果又取决于进程运行的顺序时,我们认为发生了

竞争条件(race condition)

 

7个exec函数之间的关系

函数pathnamefilenamefd参数表argv[]environenvp[]
execl    
execlp    
execle     
execv    
execvp    
execve     
fexecve    
名字中的字母 pflv e

  


一个exec的例子

  1. #include <stdio.h>
  2. #include <unistd.h>
  3. #include <stdlib.h>
  4. int main (void) {
  5. printf ("parent process...\n");
  6. pid_t pid = vfork ();
  7. if (pid == -1)
  8. perror ("vfork"), exit (1);
  9. if (pid == 0) {
  10. printf ("child process...\n");
  11. if (execle ("/bin/ls", "ls", "-l", NULL, NULL) == -1)
  12. perror ("execle"), _exit (1);
  13. }
  14. sleep (1);
  15. printf ("parent over...\n");
  16. return 0;
  17. }
  18. //strace分析程序,最终是通过 execve调用的
  19. ...
  20. vfork(strace: Process 11625 attached
  21. <unfinished ...>
  22. [pid 11625] write(1, "child process...\n", 17child process...
  23. ) = 17
  24. [pid 11625] execve("/bin/ls", ["ls", "-l"], NULL <unfinished ...>
  25. [pid 11624] <... vfork resumed> ) = 11625
  26. [pid 11624] rt_sigprocmask(SIG_BLOCK, [CHLD], [], 8) = 0
  27. [pid 11624] rt_sigaction(SIGCHLD, NULL, {SIG_DFL, [], 0}, 8) = 0
  28. [pid 11624] rt_sigprocmask(SIG_SETMASK, [], NULL, 8) = 0
  29. [pid 11624] nanosleep({1, 0}, <unfinished ...>
  30. [pid 11625] <... execve resumed> ) = 0
  31. ...

 

 

 

解释器文件和System

现在所有的UNIX系统都支持解释器文件(interpreter file),它是文本文件

  1. //形式是  
  2. #! pathname [optional-argument]  
  3.   
  4. //比如  
  5. #! /bin/sh  

pathname通常是绝对路径名,对它不进行什么特殊的处理(不适用PATH进行路径搜索)对这种文件的识别是由

内核作为exec系统调用处理的一部分来完成的。内核调用exec函数的进程世纪之星的并不是该解释器文件,而

是在该解释器文件第一行中 pathname所指定的文件。一定要将解释器文件(文本文件,它以 #! 开头)和解释器

(由该解释器我呢间第一行中的pathname指定)区分开来

比如一段程序调用一个解释器

C代码 

  1. cat /home/sar/bin/testinterp  
  2. #! /home/sar/bin/echoarg foo  
  3.   
  4. //这里echoarg是一段程序,echoarg又exec了testinterp并传入了一些参数  
  5. //执行结果  
  6. //第一个打印的是解释器的pathname,然后是参数  
  7. //接着是解释器执行后调用的命令和命令参数  
  8. ./a.out    
  9. argv[0]: /home/sar/bin/echoarg  
  10. argv[1]: foo  
  11. argv[2]: /home/sar/bin/testinterp  
  12. argv[3]: myarg1  
  13. argv[4]: MY ARG2  

system执行的例子,system最终是通过fork+execve两个系统调用实现的

  1. #include <stdio.h>
  2. #include <stdlib.h>
  3. #include <sys/wait.h>
  4. #include <sys/types.h>
  5. int main() {
  6. pid_t status;
  7. status = system("./test.sh");
  8. if (-1 == status) {
  9. printf("system error!");
  10. }
  11. else {
  12. printf("exit status value = [0x%x]\n", status);
  13. if (WIFEXITED(status)) {
  14. if (0 == WEXITSTATUS(status)) {
  15. printf("run shell script successfully.\n");
  16. }
  17. else {
  18. printf("run shell script fail, script exit code: %d\n", WEXITSTATUS(status));
  19. }
  20. }
  21. else {
  22. printf("exit status = [%d]\n", WEXITSTATUS(status));
  23. }
  24. }
  25. return 0;
  26. }

 

 

 

进程会计

大多数UNIX系统提供了一个选项以进程会计(process accounting)处理。启动该选项后,每当进程结束时内核

就会写一个会计记录。典型的会计记录包含总量较小的二进制数据,一般包括命令名,所使用的CPU时间

总量,用户ID和组ID,启动时间等

函数acct启动和禁用进程会计,唯一使用这个函数的命是accton。root执行一个带路径名参数的通常是

/var/account/acct。  在linux中该文件是/var/account/pacct

会计记录结果定义在<sys/acct.h>中

 

进程会计,结构体,会计记录所需的各个数据 (CPU时间,传递的字符数等)都由内核保存在进程表中,并在一个新进程被创建时初始化(如在fork之后在子进程中)。进程终止时写一个会计记录

1.我们不能获取永不终止的进程会计记录,如init进程

2.会计文件记录的顺序对应于进程终止的顺序,而不是他们的启动顺序,为了确定启动顺序需要读全部的会计文件,并按照启动日历时间排序,但这样并不能保证完全精确

 

会计记录中的ac_flag值

ac_flag说明
AFORK进程是由fork产生的,但从未调用exec
ASU进程使用超级用户特权
ACCORE进程转存core
AXSIG进程由一个信号杀死
AEXPND扩展的会计条目
ANVER新格式记录

 

 

 

进程优先级和CPU时间

一个调整进程优先级的例子

  1. #include <stdio.h>
  2. #include <stdlib.h>
  3. #include <unistd.h>
  4. #include <sys/resource.h>
  5. int main(int argc, char *argv[]) {
  6. char *log_name = getlogin();
  7. printf("log name->%s\n", log_name);
  8. int pro_i = getpriority(PRIO_PROCESS,getpid());
  9. nice(9527);
  10. printf("priority is %d\n",pro_i);
  11. pro_i = getpriority(PRIO_PROCESS,getpid());
  12. printf("priority is %d\n",pro_i);
  13. return 0;
  14. }
  15. //执行结果
  16. log name->root
  17. priority is 0
  18. priority is 19

一个获取cpu时间的例子

要获取运行时间,必须获取相对值。例如,调用times,保存返回值,在以后某个时间再次调用times,从新的返回值中减去
以前的返回值,此差值就是墙上时钟时间。

所有由此函数返回的clock_t值都用_SC_CLK_TCK(由sysconf函数返回的每秒时钟滴答数)变换成秒数。

  1. #include <stdio.h>
  2. #include <sys/times.h>
  3. #include <time.h>
  4. #include <unistd.h>
  5. static void pr_times(clock_t,struct tms*,struct tms*);
  6. static void do_cmd(char *);
  7. int main(int argc, char *argv[]){
  8. int i;
  9. for(i=1; i<argc; i++){
  10. do_cmd(argv[i]);
  11. }
  12. return 0;
  13. }
  14. static void do_cmd(char* cmd){
  15. struct tms tmsstart, tmsend;
  16. clock_t start, end;
  17. int status;
  18. printf("command:%s\n",cmd);
  19. if((start=times(&tmsstart)) == -1){
  20. perror("times");
  21. return;
  22. }
  23. if((status = system(cmd))<0){
  24. perror("system");
  25. return;
  26. }
  27. if((end = times(&tmsend)) == -1){
  28. perror("times");
  29. return;
  30. }
  31. pr_times(end-start, &tmsstart, &tmsend);
  32. }
  33. static void pr_times(clock_t real, struct tms* tmsstart, struct tms* tmsend){
  34. static long clktck = 0;
  35. if((clktck = sysconf(_SC_CLK_TCK))<0){
  36. perror("sysconf");
  37. return;
  38. }
  39. printf("real:%7.2f\n",real/(double)clktck);
  40. printf("user:%7.2f\n",(tmsend->tms_utime - tmsstart->tms_utime)/(double)clktck);
  41. printf("sys:%7.2f\n",(tmsend->tms_stime - tmsstart->tms_stime)/(double)clktck);
  42. printf("child user:%7.2f\n",(tmsend->tms_cutime - tmsstart->tms_cutime)/(double)clktck);
  43. printf("child sys:%7.2f\n",(tmsend->tms_cstime - tmsstart->tms_cstime)/(double)clktck);
  44. }
  45. //执行命令
  46. ./time "dd if=/dev/zero of=/dev/null bs=1M count=1000"
  47. //执行结果
  48. command:dd if=/dev/zero of=/dev/null bs=1M count=1000
  49. 1000+0 records in
  50. 1000+0 records out
  51. 1048576000 bytes (1.0 GB) copied, 0.0761412 s, 13.8 GB/s
  52. real: 0.08
  53. user: 0.00
  54. sys: 0.00
  55. child user: 0.00
  56. child sys: 0.07

 

 

 

 

 

参考

实际用户ID,有效用户ID及设置用户ID

什么是实际用户ID、有效用户ID和设置用户ID

一个fork的谜题

exec执行普通文件和解释器文件的区别

wait,waitpid,wait3,wait4

Unix环境高级编程(七)fork函数总结

system函数的详细执行过程

《unix环境高级编程》--- 进程控制

Linux文件特殊权限——SetUID、SetGID、Sticky BIT

 

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/我家小花儿/article/detail/210255
推荐阅读
相关标签
  

闽ICP备14008679号