当前位置:   article > 正文

Linux系统编程(四)进程

Linux系统编程(四)进程

一、进程的产生(fork)

fork(2) 系统调用会复制调用进程来创建一个子进程,在父进程中 fork 返回子进程的 pid,在子进程中返回 0。

  1. #include <sys/types.h>
  2. #include <unistd.h>
  3. pid_t fork(void);

fork 后子进程不继承未决信号和文件锁,资源利用量清 0。 由于进程文件描述符表也继承下来的,所以可以看到父子进程的输入输出指向都是一样的,这个特性可以用于实现基本的父子进程通信。

init() 是所有进程的祖先进程,pid = 1。

例子(fork_test.c)

  1. #include <stdio.h>
  2. #include <stdlib.h>
  3. #include <sys/types.h>
  4. #include <unistd.h>
  5. int main()
  6. {
  7. pid_t pid;
  8. printf("Begin\n");
  9. //fflush(); //!!!重要
  10. if ((pid = fork()) == 0) {
  11. // child
  12. printf("child process executed\n");
  13. exit(0);
  14. } else if (pid < 0) {
  15. perror("fork");
  16. exit(1);
  17. }
  18. // father
  19. //sleep(1);
  20. printf("parent process executed\n");
  21. exit(0);
  22. }

运行结果

注意:父子进程的运行顺序不能确定,由调度器的调度策略决定。 

面试题:当将输出重定向到文件里面时,Begin 为什么打印了两次?如下图:

答案:输出到终端默认是行缓冲模式,加 “\n” 即可刷新缓冲区,但由于重定向到文件是写文件,而写文件是全缓冲,所以 “\n” 无法刷新缓冲区,所以需要在 Begin 后加上 fflush() 来强制刷新缓冲区。

例子(primes_fork.c,通过子进程来计算质数): 

  1. #include <stdio.h>
  2. #include <stdlib.h>
  3. #include <sys/types.h>
  4. #include <unistd.h>
  5. int main()
  6. {
  7. int max = 100;
  8. pid_t pid;
  9. for (int i = 2; i <= max; i++) {
  10. if ((pid = fork()) == 0) {
  11. // child
  12. int flag = 1;
  13. for (int j = 2; j <= i / 2; j++) {
  14. if (i % j == 0) {
  15. flag = 0;
  16. break;
  17. }
  18. }
  19. if (flag) {
  20. printf("%d\n", i);
  21. }
  22. exit(0);
  23. } else if (pid < 0) {
  24. perror("fork");
  25. exit(1);
  26. }
  27. }
  28. exit(0);
  29. }

通过 man ps 可以找到进程的所有状态信息:

  • D:不可中断的睡眠态(通常是 IO);
  • I:空闲的内核线程;
  • R:运行态或可运行态;
  • S:可中断的睡眠态(等待事件的完成);
  • T:被控制信号停止;
  • X:死亡态;
  • Z:僵尸(zombie)进程,已终止但未被其父亲接收;

其中父进程如果不使用 waitpid 接收子进程状态,会导致子进程终止后变成僵尸态,会占用 pid 号,父进程终止后内核会自动将子进程交付给 init 进程,等待子进程终止后为其 “收尸”。

二、进程的消亡及释放资源(wait、waitpid)

 wait(2) 和 waitpid(2) 可以等待进程状态发生变化。

  1. #include <sys/types.h>
  2. #include <sys/wait.h>
  3. pid_t wait(int *wstatus);
  4. pid_t waitpid(pid_t pid, int *wstatus, int options);

wait(2) 成功时返回终止的子进程的 pid,不需要指定特定的子进程 pid,并且需要死等(阻塞)。 若 wstatus 非空,则其可以一些宏函数指示进程的状态:

  • WIFEXITED(wstatus):若子进程正常终止则返回真(exit(3)、_exit(2) 或从 main 函数返回);
  • WEXITSTATUS(wstatus):返回子进程的退出状态码,前置条件是 WIFEXITED(wstatus) 必须首先为真;
  • WIFSIGNALED(wstatus):若子进程被信号终止了则返回真;
  • WTERMSIG(wstatus):检测终止子进程的信号值,前置条件是 WIFSIGNALED(wstatus) 为真;

waitpid(2) 相比于 wait(2) 可以指定等待的子进程(pid),并且可以指定一些选项(options):

  • WNOHANG:如果没有子进程退出则立即返回(非阻塞)

进程分配任务的方法:

  1. 分块(每个线程一部分任务);
  2. 交叉分配(依次给每个线程分配任务);
  3. 池(往任务池里面扔任务,线程从池中抢任务);

三、exec 函数族

 exec 函数族可以用来执行一个二进制可执行文件。

  1. #include <unistd.h>
  2. extern char **environ;
  3. /* 需要给出文件路径 */
  4. int execl(const char *pathname, const char *arg, ...
  5. /* (char *) NULL */);
  6. /* 只需要文件名称,然后去环境变量environ中寻找 */
  7. int execlp(const char *file, const char *arg, ...
  8. /* (char *) NULL */);
  9. int execle(const char *pathname, const char *arg, ...
  10. /*, (char *) NULL, char *const envp[] */);
  11. int execv(const char *pathname, char *const argv[]);
  12. int execvp(const char *file, char *const argv[]);

exec 函数族会将当前进程映像替换为新的进程映像。所以在 exec 后的代码不会执行。

在 exec 之前需要 fflush(),和前面 1.1 的例子一样,写文件是全缓冲,会导致打印的内容还没写入到文件就被 exec 替换掉了进程映像。

例子,使用 fork + exec 来实现一个简单的 shell(myshell.c)

  1. #include <stdio.h>
  2. #include <stdlib.h>
  3. #include <unistd.h>
  4. #include <glob.h>
  5. #include <string.h>
  6. #include <sys/types.h>
  7. #include <sys/wait.h>
  8. #define DELIMS " \t\n"
  9. struct cmd_st
  10. {
  11. glob_t globres;
  12. };
  13. static void prompt(void)
  14. {
  15. printf("mysh$ ");
  16. }
  17. static void parse(char *line, struct cmd_st *cmd)
  18. {
  19. char *tok;
  20. int i = 0;
  21. while (1) {
  22. tok = strsep(&line, DELIMS);
  23. if (tok == NULL)
  24. break;
  25. if (tok[0] == '\0') // empty str
  26. continue;
  27. glob(tok, GLOB_NOCHECK | GLOB_APPEND * i, NULL, &cmd->globres);
  28. i = 1;
  29. }
  30. }
  31. int main()
  32. {
  33. char *linebuf = NULL;
  34. size_t linebuf_size = 0;
  35. struct cmd_st cmd;
  36. pid_t pid;
  37. while (1) {
  38. prompt(); // 打印提示符
  39. if (getline(&linebuf, &linebuf_size, stdin) < 0) {
  40. break;
  41. }
  42. parse(linebuf, &cmd); // 解析命令
  43. /* extern cmd */
  44. {
  45. pid = fork();
  46. if (pid < 0) {
  47. perror("fork");
  48. exit(1);
  49. }
  50. /* child process */
  51. if (pid == 0) {
  52. execvp(cmd.globres.gl_pathv[0], cmd.globres.gl_pathv);
  53. perror("exec");
  54. exit(1);
  55. }
  56. wait(NULL);
  57. }
  58. }
  59. exit(0);
  60. }

可以在 /etc/passwd 文件里修改用户的登录 shell,十分有趣: 

四、守护进程

持续运行在后台,等待处理请求的进程。一次成功的登录会产生一个会话(session)

管道符:把第一个命令的标准输出作为第二个命令的标准输入(ls | more)。

Linux--setsid() 与进程组、会话、守护进程

例子(mydaemon.c)

  1. #include <stdio.h>
  2. #include <stdlib.h>
  3. #include <unistd.h>
  4. #include <sys/types.h>
  5. #include <sys/stat.h>
  6. #include <fcntl.h>
  7. #define FILENAME "/tmp/out"
  8. static void daemonize(void)
  9. {
  10. pid_t pid;
  11. int fd;;
  12. if ((pid = fork()) < 0) {
  13. perror("fork");
  14. exit(1);
  15. }
  16. if (pid == 0) { // child process
  17. if ((fd = open("/dev/null", O_RDWR)) < 0) {
  18. perror("open");
  19. exit(1);
  20. }
  21. dup2(fd, 0);
  22. dup2(fd, 1);
  23. dup2(fd, 2);
  24. if (fd > 2)
  25. close(fd);
  26. setsid();
  27. // change working directory
  28. chdir("/"); // preventing "device is busy"
  29. // umask(0);
  30. return;
  31. } else {
  32. exit(0);
  33. // the daemon process's parent will be the init process
  34. }
  35. }
  36. int main()
  37. {
  38. FILE* fp = NULL;
  39. // init daemon process
  40. daemonize();
  41. // the task of daemon process
  42. if ((fp = fopen(FILENAME, "w")) == NULL) {
  43. perror("fopen");
  44. exit(1);
  45. }
  46. for (int i = 0; ; i++) {
  47. fprintf(fp, "%d\n", i);
  48. fflush(fp); // writting file is full buffer, so we should flush the buffer after printf()
  49. sleep(1);
  50. }
  51. exit(0);
  52. }

编译运行程序后使用 ps -axj 可以看到 daemon 进程在后台运行,但是发现其 PPID(父进程 pid)不是 init 进程的 pid 1,查了一下发现是在 Ubuntu18.04 系统中,孤儿进程会被 “/lib/systemd/systemd --user” 进程领养。 

pid 为 1097 对应的进程为 /lib/systemd/systemd --user:

syslogd 服务:

  • openlog() 打开系统日志的连接;
  • syslog() 提交日志;
  • closelog() 关闭系统日志的连接;
声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/从前慢现在也慢/article/detail/92189
推荐阅读
相关标签
  

闽ICP备14008679号