Linux系统编程(四)进程

一、进程的产生(fork)

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

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

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

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

例子(fork_test.c)

#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <unistd.h>int main()
{pid_t pid;printf("Begin\n");//fflush(); //!!!重要if ((pid = fork()) == 0) {// childprintf("child process executed\n");exit(0);} else if (pid < 0) {perror("fork");exit(1);}// father//sleep(1);printf("parent process executed\n");exit(0);
}

运行结果

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

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

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

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

#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <unistd.h>int main()
{int max = 100;pid_t pid;for (int i = 2; i <= max; i++) {if ((pid = fork()) == 0) {// childint flag = 1;for (int j = 2; j <= i / 2; j++) {if (i % j == 0) {flag = 0;break;}}if (flag) {printf("%d\n", i);}exit(0);} else if (pid < 0) {perror("fork");exit(1);}}exit(0);
}

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

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

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

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

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

#include <sys/types.h>
#include <sys/wait.h>pid_t wait(int *wstatus);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 函数族可以用来执行一个二进制可执行文件。

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

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

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

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

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <glob.h>
#include <string.h>
#include <sys/types.h>
#include <sys/wait.h>#define DELIMS " \t\n"struct cmd_st
{glob_t globres;
};static void prompt(void)
{printf("mysh$ ");
}static void parse(char *line, struct cmd_st *cmd)
{char *tok;int i = 0;while (1) {tok = strsep(&line, DELIMS);if (tok == NULL)break;if (tok[0] == '\0') // empty strcontinue;glob(tok, GLOB_NOCHECK | GLOB_APPEND * i, NULL, &cmd->globres);i = 1;}
}int main()
{char *linebuf = NULL;size_t linebuf_size = 0;struct cmd_st cmd;pid_t pid;while (1) {prompt(); // 打印提示符if (getline(&linebuf, &linebuf_size, stdin) < 0) {break;}parse(linebuf, &cmd); // 解析命令/* extern cmd */{pid = fork();if (pid < 0) {perror("fork");exit(1);}/* child process */if (pid == 0) {execvp(cmd.globres.gl_pathv[0], cmd.globres.gl_pathv);perror("exec");exit(1);}wait(NULL);}}exit(0);
}

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

四、守护进程

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

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

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

例子(mydaemon.c)

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>#define FILENAME "/tmp/out"static void daemonize(void)
{pid_t pid;int fd;;if ((pid = fork()) < 0) {perror("fork");exit(1);}if (pid == 0) { // child processif ((fd = open("/dev/null", O_RDWR)) < 0) {perror("open");exit(1);}dup2(fd, 0);dup2(fd, 1);dup2(fd, 2);if (fd > 2)close(fd);setsid();// change working directorychdir("/"); // preventing "device is busy"// umask(0);return;} else {exit(0);// the daemon process's parent will be the init process}
}int main()
{FILE* fp = NULL;// init daemon processdaemonize();// the task of daemon processif ((fp = fopen(FILENAME, "w")) == NULL) {perror("fopen");exit(1);}for (int i = 0; ; i++) {fprintf(fp, "%d\n", i);fflush(fp); // writting file is full buffer, so we should flush the buffer after printf()sleep(1);}exit(0);
}

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

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

syslogd 服务:

  • openlog() 打开系统日志的连接;
  • syslog() 提交日志;
  • closelog() 关闭系统日志的连接;

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/470195.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【MySQL】高度为2和3时B+树能够存储的记录数量的计算过程

文章目录 题目答案高度为2时的B树高度为3时的B树总结 GPT4 对话过程 题目 InnoDB主键索引的Btree在高度分别为 2 和 3 时&#xff0c;可以存储多少条记录&#xff1f; 答案 高度为2时的B树 计算过程&#xff1a; 使用公式 ( n 8 ( n 1 ) 6 16 1024 ) (n \times 8 …

一文搞懂“什么是双亲委派”

文章目录 双亲委派介绍类加载器介绍类加载流程验证自定义类加载器为什么要设计这种机制 提前声明&#xff1a;以下介绍都是基于jdk9之前版本的双亲委派机制&#xff0c;jdk9及之后版本双亲委派会有变化&#xff0c;不在本篇介绍。 双亲委派介绍 双亲委派机制&#xff08;Pare…

[Vue warn]: Duplicate keys detected: ‘1‘. This may cause an update error.

[Vue warn]: Duplicate keys detected: ‘1‘. This may cause an update error.——> Vue报错&#xff0c;key关键字不唯一&#xff1a; 解决办法&#xff1a;修改一下重复的id值&#xff01;&#xff01;&#xff01;

并发CPU伪共享及优化

目录 伪共享 解决 伪共享 缓存系统中是以缓存行&#xff08;cache line&#xff09;为单位存储的。缓存行是2的整数幂个连续字节&#xff0c;一般为32-256个字节。最常见的缓存行大小是64个字节。当多线程修改互相独立的变量时&#xff0c;如果这些变量共享同一个缓存行&am…

459. Repeated Substring Pattern( 重复的子字符串)

问题描述 给定一个非空的字符串 s &#xff0c;检查是否可以通过由它的一个子串重复多次构成。 问题分析 如果一个字符串s能够由其子字符串重复多次构成&#xff0c;那么这个子字符串的长度 l l l一定是字符串s长度 L L L的因子&#xff0c;即 L / l z ( z ∈ 整数 ) L/lz …

【Linux学习】线程池

目录 23.线程池 23.1 什么是线程池 23.2 为什么需要线程池 23.3 线程池的应用场景 23.4 实现一个简单的线程池 23.4.1 RAII风格信号锁 23.4.2 线程的封装 23.4.3 日志打印 22.4.4 定义队列中存放Task类任务 23.4.5 线程池的实现(懒汉模式) 为什么线程池中需要有互斥锁和条件变…

云备份项目:在云端保护您的数据【二、开发】

☘️过度的信息对一个过着充实生活的人来说&#xff0c;是一种不必要的负担☘️ 文章目录 前言工具类实现文件实用工具类代码实现 Json实用工具类代码实现 服务端单例配置类系统配置信息单例配置类 数据管理类数据信息数据管理 热点管理类业务处理类 客户端数据管理类文件备份类…

Pytest测试技巧之Fixture:模块化管理测试数据

在 Pytest 测试中&#xff0c;有效管理测试数据是提高测试质量和可维护性的关键。本文将深入探讨 Pytest 中的 Fixture&#xff0c;特别是如何利用 Fixture 实现测试数据的模块化管理&#xff0c;以提高测试用例的清晰度和可复用性。 什么是Fixture&#xff1f; 在 Pytest 中&a…

第13章 网络 Page734 “I/O对象”的链式传递 单独的火箭发射函数,没有用对的智能指针

上一篇博文中&#xff0c;我们使用单独的火箭发射函数&#xff0c;结果什么结果也没有得到&#xff0c;原因是launch_rocket()函数结束时&#xff0c;其内的局部对象counter生命周期也结束了 那么可以将counter改为指针吗&#xff1f;在堆中分配&#xff0c;这样当函数退出时&…

B2科目二考试项目笔记

B2科目二考试项目笔记 1 桩考1.1 右起点倒库1.2 移库&#xff08;左→右&#xff09;1.3 驶向左起点1.4 左起点倒库1.5 驶向右起点 2 侧方停车考试阶段&#xff08;从路边开始&#xff09;&#xff1a; 3 直角转弯4 坡道定点停车和起步5 单边桥6 通过限速限宽门7 曲线行驶8 连续…

第21讲关于我们页面实现

关于我们页面实现 关于锋哥页面author.vue 我们这里用一个vip宣传页面&#xff0c;套一个web-view <template><web-view src"http://www.java1234.com/vip.html"></web-view> </template><script> </script><style> <…

属性/成员变量

一、属性/成员变量 二、注意事项 三、创建对象