C/C++ WebServer 2 Linux 多进程开发

Meqt

进程概述

进程是正在运行的程序的实例。是一个具有一定独立功能的程序关于某个数据集合的一次运行活动。它是操作系统动态执行的基本单元,在传统的操作系统中,进程既是基本的分配单元,也是基本的执行单元。
程序不占CPU、Memory,进程占用CPU和Memory
可以用一个程序来创建多个进程,进程是由内核定义的抽象实体,并为该实体分配用以执行程序的各项系统资源。从内核的角度看,进程由用户内存空问和一系列内核数据结构组成,其中用户内存空间包含了程序代码及代码所使用的变量,而内核数据结构则用于维护进程状态信息。记录在内核数据结构中的信息包括许多与进程相关的标识号(IDs)、虚拟内存表、打开文件的描述符表、信号传递及处理的有关信息、进程资源使用及限制、当前工作目录和大量的其他信息。
内核数据

  • 标识号
  • 虚拟内存表
  • 文件描述符表
  • 信号传递及处理的有关信息
  • 进程资源使用及限制
  • 当前工作目录和大量的其他信息。

单道程序
多道程序设计
Timeslice
Parallel: multiprocessors
Concurrency: single processor
PCB: Processing Control Block struct task_struct

1
2
3
4
5
6
7
8
9
10
11
12
13
14
struct task_struct{
int pid_t;
//进程状态
// 保存和恢复的一些CPU寄存器
//虚拟地址空间
//控制终端的信息
//current working directory
// umask
// file describer
// signal related infor
// uid gid
// session and process group
//
}
1
2
ulimit -a 
ulimit -n

进程状态转换

三态模型:就绪、运行、阻塞
五态模型:新建、就绪、运行、阻塞、终止

1
2
$ ps aux
$ ps j
arg usage
a all
u 详细信息
x 没有控制终端的进程
j 作业进程
1
2
3
$ top
$ kill //kill process
$ ./a.out & #后代运行
1
2
3
pid_t getpid(void);
pid_t getppid(void); // father pid
pid_t getpgid(pid_t pid); //

进程创建

1
2
3
4
#include <sys/types.h>
#include <unistd.h>

pid_t fork(void);
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
#include <sys/types.h>
#include <unistd.h>
#include <stdio.h>

int main() {
pid_t pid = fork();
if (pid>0){
// father process
printf("pid:%d, ppid:%d\n)", pid, getppid());
printf("I'm parent process, pid=%d\n", getpid());
}else if (pid==0){
// child process
printf("I'm child process, pid=%d, ppid=%d\n", getpid(), getppid());
}

for (int i=0; i<5; i++) {
printf("i:%d, pid:%d, ppid:%d\n", i, getpid(), getppid());
sleep(1);
}

return 0;
}

父子进程虚拟地址空间情况

Copy on write: 共享地址空间,只在需要时复制。父子进程共享文件(fd)
读时共享,写时复制。

父子进程关系及GDB多进程调试

父子进程的共同点:

  • 开始时共享
  • 读时共享,写时拷贝
    GDB多进程调试
    gdb默认跟踪父进程
    1
    2
    3
    set follow-fork mode [parent|child]
    set detach-on-fork [on|off] 是否脱离gdb调试
    info inferiors

exec函数族

在调用进程内部执行一个可执行文件

exec(“a.out”)
execl
execlp
execle
execv
execvp
execvpe
execve
l: list 参数地址列表
v(vector) 存有各参数地址的指针数组的地址
p(path) 按 PATH 环境变量指定的目录搜索可执行文件
e(environment) 存有环境变量字符串地址的指针数组的地址

1
2
3
4
5
6
7
8
9
10
11
12
13
// man 3 exec
#include <unistd.h>

extern char **environ;

int execl(const char *path, const char *arg, ... ); // /bin/ps
// 参数最后以NULL结尾
int execlp(const char *file, const char *arg, ...); //会自己在path(env 查看)中查找, 可写ps
int execle(const char *path, const char *arg, ...);
int execv(const char *path, char *const argv[]);
int execvp(const char *file, char *const argv[]);
int execvpe(const char *file, char *const argv[],
char *const envp[]);
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// execl.c
#include <unistd.h>
#include <stdio.h>
int main() {
if (fork()>0) {
printf("parent process, pid:%d\n", getpid());
}else{
execl("hello", "hello", NULL);
printf("I'm child process, pid:%d, ppid:%d\n", getpid(), getppid());
}

for (int i=0; i<3; i++) {
printf("i:%d, pid:%d\n", i, getpid());
}
return 0;
}

Output

parent process, pid:26979
i:0, pid:26979
i:1, pid:26979
i:2, pid:26979
Hello, world!

In child process, nothing will happen after execl(子进程内存印象被替换,无返回)

进程退出、孤儿进程、僵尸进程

进程退出

1
2
3
4
5
#include <unistd.h>
void _exit(int status);

#include <stdio.h>
void exit(int status);

父进程回收子进程资源

1
2
3
4
5
6
7
8
9
10
11
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

int main() {
printf("hello\n");
printf("world");

_exit(0);
return 0;
}

hello
exit()会有IO处理,_exit()无,所以不会刷新缓冲区,不会printf(“world”);

孤儿进程:

父进程先死了,把父进程设置为init,为了回收资源
孤儿进程不会有什么危害

僵尸进程 zombie

进程终止时,父进程尚未回收
不能被kill -9杀死,无法释放进程号

1
2
3
4
5
6
7
8
9
10
11
12
13
#include<sys/types.h>
#include<unistd.h>
#include<stdio.h>

int main() {
if (fork()){
while(1) {
printf("I'm parent process, pid:%d, ppid:%d\n", getpid(), getppid());
sleep(1);
}
}else printf("I'm child process, pid=%d, ppid=%d\n", getpid(), getppid());
return 0;
}
1
ps aux

可以看见状态为 Z的进程

wait 函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
// man 2 wait
#include <sys/types.h>
#include <sys/wait.h>
pid_t wait(int *wstatus);
pid_t waitpid(pid_t pid, int *wstatus, int options);

// wait
#include <sys/types.h>
#include <sys/wait.h>
#include <stdio.h>
#include <unistd.h>

int main() {
pid_t pid;
for (int i=0; i<5; i++) {
pid = fork();
if (pid==0) break;
}
if (pid>0) {
while (1) {
printf("I'm parent, pid:%d\n", getpid());
int st;
int ret = wait(&st);
if (ret==-1) break;
printf("exited with %d\n", WTERMSIG(st));
printf("child die, pid:%d\n", ret);
sleep(1);
}
}else if (pid==0) {
while(1) {
printf("I'm child process, pid:%d, ppid:%d\n", getpid(), getppid());
sleep(1);
}
}
return 0;
}

waitpid 函数

可以设置是否阻塞

The value of pid can be:
< -1 meaning wait for any child process whose process group ID is
equal to the absolute value of pid.
-1 meaning wait for any child process.
0 meaning wait for any child process whose process group ID is
equal to that of the calling process.
0 meaning wait for the child whose process ID is equal to the
value of pid.
The value of options is an OR of zero or more of the following con‐
stants:
WNOHANG return immediately if no child has exited.
WUNTRACED also return if a child has stopped (but not traced via
ptrace(2)). Status for traced children which have stopped
is provided even if this option is not specified.
WCONTINUED (since Linux 2.6.10)
also return if a stopped child has been resumed by delivery
of SIGCONT.
waitpid(): on success, returns the process ID of the child whose state
has changed; if WNOHANG was specified and one or more child(ren) speci‐
fied by pid exist, but have not yet changed state, then 0 is returned.
On error, -1 is returned.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
#include <sys/types.h>
#include <sys/wait.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

int main() {
int ret;
for (int i=0; i<5; i++) {
ret = fork();
if (!pid) break;
}

if (pid) {
while (1) {
sleep(1);
printf("I'm parent process, pid:%d\n", getpid());
int st;
int res = waitpid(-1, &st, WNOHANG);
if (res==-1) {
break;
}else if (res==0) {
continue;
}else{
printf("child process pid:%d die\n", res);
}
}
}else{
while (1) {
sleep(1);

printf("I'm child process, pid:%d, ppid:%d\n", getpid(), getppid());
}
}
}

进程间通信简介

Inter Process Communication
目的:数据传输、通知事件、资源共享、进程控制
同一主机进程间的通信
UNIX:匿名管道,有名管道,信号
System V/POSIX:消息队列、共享内存、信号量
不同主机(网络)进程间通信:Socket

匿名管道概述

1
ls | wc -l $ | 管道符
  • 管道其实是一个在内核内存中维护的缓冲器,这个缓冲器的存储能力是有限的,不同的操作系统大小不一定相同。
  • 管道拥有文件的特质:读操作、写操作,匿名管道没有文件实体,有名管道有文件实体,但不存储数据。可以按照操作文件的方式对管道进行操作。
  • 一个管道是一个字节流,使用管道时不存在消息或者消息边界的概念,从管道读取数据的进程可以读取任意大小的数据块.而不管写入进程写入管道的数据块的大小是多少。
  • 通过管道传递的数据是顺序的,从管道中读取出来的字节的顺序和它们被写入管道的顺序是完全一样的。
  • 单向,半双工
  • 数据一次性操作,读完即扔。不能使用lseek来随机的访问数据
  • 只能在具有公共祖先的进程中使用,父子|兄弟 (共用文件描述符表)
  • 数据结构:循环队列
    1
    2
    3
    4
    5
    #include <unistd.h>
    /*create pipe */
    int pipe(int pipefd[2]);
    /* show pipe buffer limit */
    long fpathconf(int fd, int name);
    1
    2
    # pipe size 
    ulimit -a

父子进程通过匿名管道通信

1
2
3
4
5
6
7
8
9
10
11
12
13
#include <unistd.h>
int pipe(int pipefd[2]);
/**
pipe() creates a pipe, a unidirectional data channel that can be used
for interprocess communication. The array pipefd is used to return two
file descriptors referring to the ends of the pipe. pipefd[0] refers
to the read end of the pipe. pipefd[1] refers to the write end of the
pipe. Data written to the write end of the pipe is buffered by the
kernel until it is read from the read end of the pipe.
@param[out] pipefd[0] read end
@param[out] pipefd[1] write end
@return 0 success, -1 failed
*/
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <unistd.h>
#include <string.h>

int main() {
int pipefd[2];
if (pipe(pipefd)==-1) perror("pipe");

pid_t pid = fork();
if (pid==-1) {
perror("fork");
exit(0);
}else if (pid==0) {
printf("I'm child process, pid:%d, ppid:%d\n", getpid(), getppid());
char *str = "hello, I'm child";
char buf[1024] = {0};
while(1) {
write(pipefd[1], str, strlen(str));
sleep(1);
read(pipefd[0], buf, sizeof(buf));
printf("child received %s\n", buf);
}
}else{
printf("I'm parent process, pid:%d\n", getpid());
char buf[1024] = {0};
char *str = "I'm parent";
while(1) {
sleep(1);
int len = read(pipefd[0], buf, sizeof(buf));
printf("parent received %s, pid:%d\n", buf, getpid());
write(pipefd[1], str, strlen(str));
}
}
return 0;
}

管道默认阻塞
开发中不要自己写,防止自己读到自己写的程序

匿名管道通信案例

1
ps aux | grep root
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
/* parent child pipe */
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <unistd.h>
#include <string.h>
#include <wait.h>

int main() {
int pipefd[2];
pipe(pipefd);

pid_t pid = fork();

if (pid>0) {
//TODO parent process
close(pipefd[1]);
char buf[1024];
int len = -1;
while ( (len=read(pipefd[0], buf, sizeof(buf)-1)) > 0) {
printf("%s", buf);
bzero(buf, sizeof(buf));
}
wait();
}else if(pid==0){
//TODO child process
dup2(pipefd[1], STDOUT_FILENO);
close(pipefd[0]);
execlp("ps", "ps", "aux", NULL);
}else{
perror("fork");
exit(0);
}
}

管道的读写特点和管道设置为非阻塞

  1. pipefd[1] 引用计数为0,read完之后读,return 0;
  2. pipefd[1]引用计数不位0,数据被读完,read 阻塞;
  3. 读端引用计数为0,写数据,SIGPIPE信号,异常终止。
  4. 读端引用计数不位0,写满数据,write会阻塞
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    /* noblock.c */
    #include <stdio.h>
    #include <stdlib.h>
    #include <sys/types.h>
    #include <unistd.h>
    #include <string.h>
    #include <fcntl.h>

    int main() {
    int pipefd[2];
    if (pipe(pipefd)==-1) perror("pipe");

    pid_t pid = fork();
    if (pid==-1) {
    perror("fork");
    exit(0);
    }else if (pid==0) {
    //TODO: Child Process
    close(pipefd[0]);
    printf("I'm child process, pid:%d, ppid:%d\n", getpid(), getppid());
    char *str = "hello, I'm child";
    while(1) {
    write(pipefd[1], str, strlen(str));
    sleep(10);
    }
    }else{
    //TODO: Parent process
    close(pipefd[1]);
    printf("I'm parent process, pid:%d\n", getpid());
    // pipefd[0] noblock
    int flag = fcntl(pipefd[0], F_GETFL);
    flag |=O_NONBLOCK;
    fcntl(pipefd[0], F_SETFL, flag);
    char buf[1024] = {0};
    while(1) {
    int len = read(pipefd[0], buf, sizeof(buf));
    printf("parent received len:%d, %s, pid:%d\n", len, buf, getpid());
    memset(buf, 0, sizeof(buf));
    sleep(1);
    }
    }
    return 0;
    }

有名管道介绍及使用

FIFO

1
mkfifo $(name)
1
2
3
#include <sys/types.h>
#include <sys/stat.h>
int mkfifo(const char *pathname, mode_t mode);

同样不支持lseek

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
/* write.c */
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <fcntl.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <stdlib.h>

int main() {

if (access("testfifo", F_OK|R_OK | W_OK) == -1) {
printf("file not exist\n");
if (mkfifo("testfifo", 0664)==-1) {
perror("mkfifo");
exit(0);
}
}else{
printf("file exist\n");
}

int fd = open("testfifo", O_WRONLY);
if (fd==-1) {
perror("open");
exit(0);
}

for (int i=0; i<100; i++) {
char buf[1024];
sprintf(buf, "Hello, fifo");
printf("write data: %s, %d\n", buf, i);
write(fd, buf, strlen(buf));
}

close(fd);
return 0;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
/* read.c */
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <fcntl.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <stdlib.h>

int main() {
int fd;
if ( (fd=open("testfifo", O_RDONLY)) == -1) {
perror("open");
exit(0);
}

while (1) {
char buf[1024] = {0};
int len = read(fd, buf, sizeof(buf));
if (len==0) {
printf("connection lost\n");
break;
}
printf("read received: %s\n", buf);
}
close(fd);
return 0;
}

阻塞

有名管道实现简单版聊天功能

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
/* chatA */
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>

int main() {
if (access("fifo1", F_OK)==-1) {
printf("filo1 not exist\n");
if (mkfifo("fifo1",0664) ==-1) {
perror("mkfifo");
exit(-1);
}
}else printf("fifo1 already exist\n");


if (access("fifo2", F_OK)==-1) {
printf("filo2 not exist\n");
if (mkfifo("fifo2",0664) ==-1) {
perror("mkfifo");
exit(-1);
}
}else printf("fifo2 already exist\n");

int fdw = open("fifo1", O_WRONLY);
if (fdw==-1) {
perror("open");
exit(0);
}else printf("open fifo1\n");

int fdr = open("fifo2", O_RDONLY);
if (fdr==-1) {
perror("open");
exit(0);
}else printf("open fifo2\n");

char buf[128] = {0};
while(1) {
memset(buf, 0, sizeof(buf));
fgets(buf, 128, stdin);
if (write(fdw, buf, strlen(buf))==-1) exit(0);

memset(buf, 0, sizeof(buf));
int ret = read(fdr, buf, sizeof(buf));
if (ret <= 0) {
perror("read");
break;
}
printf("receive buf %s\n", buf);
}
close(fdr);
close(fdw);
return 0;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
/* chatB */
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>

int main() {
if (access("fifo1", F_OK)==-1) {
printf("filo2 not exist\n");
if (mkfifo("fifo1",0664) ==-1) {
perror("mkfifo");
exit(-1);
}
}else{
printf("fifo1 already exist\n");
}

if (access("fifo2", F_OK)==-1) {
printf("filo2 not exist\n");
if (mkfifo("fifo2",0664) ==-1) {
perror("mkfifo");
exit(-1);
}
}else{
printf("fifo2 already exist\n");
}

int fdr = open("fifo1", O_RDONLY);
if (fdr==-1) {
perror("open");
exit(0);
}else printf("open fifo1\n");

int fdw = open("fifo2", O_WRONLY);
if (fdw==-1) {
perror("open");
exit(0);
}else printf("open fifo1\n");

char buf[128] = {0};
while(1) {
//TODO read
memset(buf, 0, sizeof(buf));
int ret = read(fdr, buf, sizeof(buf));
if (ret<=0) {
perror("read");
break;
}
printf("buf: %s\n", buf);

//TODO write
memset(buf, 0, sizeof(buf));
fgets(buf, 128, stdin);
if (write(fdw, buf, strlen(buf))==-1) exit(0);
}

close(fdr);
close(fdw);
return 0;
}

注意两个fifo的打开方式,防止都阻塞了。

内存映射(1)

可实现有血缘和无血缘关系的内存之间的通信。

#include <sys/mman.h>
void *mmap(void addr, size_t length, int prot, int flags, int fd, off_t offset);
int munmap(void addr, size_t length); / 释放内存映射 /
/

mmap() creates a new mapping in the virtual address space of the call‐
ing process. The starting address for the new mapping is specified in
addr. The length argument specifies the length of the mapping (which
must be greater than 0).
If addr is NULL, then the kernel chooses the address at which to create
the mapping; this is the most portable method of creating a new map‐
ping. If addr is not NULL, then the kernel takes it as a hint about
where to place the mapping; on Linux, the mapping will be created at a
nearby page boundary. The address of the new mapping is returned as
the result of the call.
The contents of a file mapping (as opposed to an anonymous mapping; see
MAP_ANONYMOUS below), are initialized using length bytes starting at
offset offset in the file (or other object) referred to by the file
descriptor fd. offset must be a multiple of the page size as returned
by sysconf(_SC_PAGE_SIZE).
The prot argument describes the desired memory protection of the map‐
ping (and must not conflict with the open mode of the file). It is
either PROT_NONE or the bitwise OR of one or more of the following
flags:
PROT_EXEC Pages may be executed.
PROT_READ Pages may be read.
PROT_WRITE Pages may be written.
PROT_NONE Pages may not be accessed.
*/

有血缘:在父进程中创建内存映射区
无血缘:创建磁盘文件,各进程得到file describor
内存映射区通信非阻塞

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
/* mmap-parent-child-ipc.c */
#include <stdio.h>
#include <sys/mman.h>
#include <unistd.h>
#include <fcntl.h>
#include <stdlib.h>
#include <sys/types.h>
#include <string.h>
#include <wait.h>

int main() {
int fd = open("test.txt", O_RDWR);
int size = lseek(fd, 0, SEEK_END);

void *ptr = mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
if (ptr == MAP_FAILED) {
perror("mmap");
exit(0);
}

char buf[64];
strcpy(buf, (char*)ptr);
printf("child received data: %s\n", buf);
// child process
strcpy((char*)ptr, "nihao, kid.");
}

munmap(ptr, size);
close(fd);
return 0;
}

内存映射(2)

  • 如果对mmap的返回值(ptr)做++操作(ptr++),munmap是否能够成功?
    ptr++可做操作,但munmap不成功
  • 如果open时O_RDONLY,mmap时prot参数指定PROT_READ | PROT_WRITE会怎样?
    会返回MAP_FAILED
  • 如果文件偏移量为1000会怎样?
    会失败,必须是页的整数倍
  • mmap什么情況下会调用失败?
    • Length = 0
    • prot只有写权限 / fd与prot不匹配
  • 可以open的时候O_CREAT一个新文件来创建映射区吗?
    可,但文件大小要拓展 lseek,trancate
  • 且mmap后关闭文件描述符,对mmap映射有没有影响?
    映射区存在,但
  • 对ptr越界操作会怎样?
    段错误
    注意关闭顺序,小心文件过大
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    // copy.c
    #include <unistd.h>
    #include <stdio.h>
    #include <sys/mman.h>
    #include <sys/types.h>
    #include <sys/stat.h>
    #include <sys/fcntl.h>
    #include <string.h>
    #include <stdlib.h>

    int main() {
    int fd = open("english.txt", O_RDWR);
    if (fd==-1) exit(0);
    int size = lseek(fd, 0, SEEK_END);

    int fd2 = open("cpy.txt", O_RDWR|O_CREAT, 0664);
    if (fd2==-1) exit(0);

    truncate("cpy.txt", size);
    write(fd2, " ", 1);

    void *ptr=mmap(NULL, size, PROT_READ, MAP_SHARED, fd, 0);
    close(fd);
    void *ptr2=mmap(NULL, size, PROT_READ|PROT_WRITE, MAP_SHARED, fd2, 0);

    if (ptr==MAP_FAILED | ptr2==MAP_FAILED) exit(0);
    memcpy(ptr2, ptr, size);
    munmap(ptr2, size);
    munmap(ptr, size);
    close(fd2);

    return 0;
    }
    匿名映射,flag MAP_ANONYMOUS
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    // anonymous.c
    #include <sys/types.h>
    #include <sys/stat.h>
    #include <sys/mman.h>
    #include <fcntl.h>
    #include <unistd.h>
    #include <stdio.h>
    #include <stdlib.h>
    #include <string.h>
    #include <wait.h>
    int main() {
    void* ptr = mmap(NULL, 4096, PROT_READ|PROT_WRITE, MAP_SHARED|MAP_ANONYMOUS, -1, 0);
    if (ptr == MAP_FAILED) exit(0);

    // parent child communication
    pid_t pid = fork();

    if (pid>0) {
    strcpy((char*)ptr, "Hello, child.");
    wait(NULL);
    }else{
    sleep(1);
    printf("%s\n", (char*)ptr);
    }

    if (munmap(ptr, 4096)==-1) exit(0);

    return 0;
    }

信号概述

软件中断,异步通信方式。
前台:stdio
异常:
系统状态变换
kill函数
特点

  • 简单
  • 不能携带大量信息
  • 满足特定条件才发送
  • 优先级比较高
    1
    2
    # 查看kill 的引用列表
    kill -l

    2 SIGINT
    3 SIGQUIT
    9 SIGKILL
    11 SIGSEGV
    13 SIGPIPE 管道破裂
    17 SIGCHILD 子进程计数
    18 SIGCONT 进程已停止使其继续运行
    19 SIGSTOP 暂停信号的执行

信号的默认处理动作

1
man 7 signal
  • Term Default action is to terminate the process.
  • Ign Default action is to ignore the signal.
  • Core Default action is to terminate the process and dump core (see
    core(5)).
  • Stop Default action is to stop the process.
  • Cont Default action is to continue the process if it is currently
    stopped.

信号的几种状态:产生、末決、递达
SIGKILI 和 SIGSTOP 信号不能被捕捉、阻塞或者忽略,只能执行默认动作。

kill、raise、abort函数

1
2
ulimit -a 
ulimit -c 1024 #设置core文件大小为1024
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
#include <sys/types.h>
#include <signal.h>

int kill(pid_t pid, int sig);
/*
If pid is positive, then signal sig is sent to the process with the ID
specified by pid.

If pid equals 0, then sig is sent to every process in the process group
of the calling process.

If pid equals -1, then sig is sent to every process for which the call‐
ing process has permission to send signals, except for process 1
(init), but see below.

If pid is less than -1, then sig is sent to every process in the
process group whose ID is -pid.
*/

#include <signal.h>
int raise(int sig);
/* The raise() function sends a signal to the calling process or thread.
In a single-threaded program it is equivalent to
*/
#include <stdlib.h>
void abort(void);
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// kill.c
#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
#include <signal.h>

int main() {
pid_t pid = fork();
if (pid==0) {
for (int i=0; i<5; i++) {
printf("%d\n", i);
sleep(1);
}
}else {
printf("parent process \n");
sleep (2);
printf("kill child process now\n");
kill(pid, SIGINT);
}
}

alarm 函数

1
2
3
#include <unistd.h>
unsigned int alarm(unsigned int seconds);
// 如果seconds为0,参数无效,不发信号

不阻塞

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// alarm.c
#include <unistd.h>
#include <stdio.h>

int main() {
int seconds = alarm(10);
printf("seconds: %d\n", seconds);
sleep(2);
seconds = alarm(2);
printf("seconds: %d\n", seconds);

while(1) {
}

return 0;
}
1
2
3
4
5
6
7
8
9
10
11
12
// alarm1.c
#include <stdio.h>
#include <unistd.h>

int main() {
int seconds = alarm(1);
int i=0;
while(1) {
printf("%d\n", i++);
}
return 0;
}

setitimer 定时器函数

1
2
3
4
5
#include <sys/time.h>

int getitimer(int which, struct itimerval *curr_value);
int setitimer(int which, const struct itimerval *new_value,
struct itimerval *old_value);
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// setitimer.c
#include <sys/time.h>
#include <stdio.h>
#include <stdlib.h>

int main() {
struct itimerval new_value;
new_value.it_interval.tv_sec = 2;
new_value.it_interval.tv_usec = 0;
new_value.it_value.tv_sec = 3;
new_value.it_value.tv_usec = 0;

int ret = setitimer(ITIMER_REAL, &new_value, NULL);
printf("Itimer start.\n");
if (ret==-1) {
perror("setitimer");
exit(0);
}

getchar();
return 0;
}

signal 信号捕捉函数

1
2
3
4
5
#include <signal.h>

typedef void (*sighandler_t)(int);
sighandler_t signal(int signum, sighandler_t handler);
/* The signals SIGKILL and SIGSTOP cannot be caught or ignored. */
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
/* signal.c */
#include <stdio.h>
#include <signal.h>
#include <sys/time.h>
#include <stdlib.h>

/**
* alarm Handler
* @param num signal caught
*/
void myalarmHandler(int num) {
printf("catch signal:%d\n", num);
return;
}

int main() {
// signal(SIGALRM, SIG_IGN);
signal(SIGALRM, myalarmHandler);

struct itimerval new_value;
new_value.it_interval.tv_sec = 2;
new_value.it_interval.tv_usec = 0;
new_value.it_value.tv_sec = 3;
new_value.it_value.tv_usec = 0;

int ret = setitimer(ITIMER_REAL, &new_value, NULL);
printf("Itimer start.\n");
if (ret==-1) {
perror("setitimer");
exit(0);
}

getchar();
return 0;
}

信号集及相关函数

许多信号相关的系统调用都需要能表示一组不同的信号,多个信号可使用一个称之为信号集的数据结构来表示,其系统数据类型为 sigset_t。
在PCB 中有两个非常重要的信号集。一个称之为 “阻塞信号集”,另一个称之为 “未决信号集”。这两个信号集都是内核使用位图机制来实现的。但操作系统不允许我们直接对这两个信号集进行位操作。而需自定义另外一个集合,借助信号集操作函数来对 PCB 中的这两个信号集进行修改。
未决、阻塞

1
int sigemptyset(sigset_t *set);
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
#include <stdio.h>
#include <signal.h>

int main() {
sigset_t set;
sigemptyset(&set);
int ret = sigismember(&set, SIGINT);
if (ret==0) printf("SIGINT not block\n");
if (ret==1) printf("SIGINT block\n");
sigaddset(&set, SIGINT);
sigaddset(&set, SIGQUIT);
ret = sigismember(&set, SIGINT);
if (ret==0) printf("SIGINT not block\n");
if (ret==1) printf("SIGINT block\n");

sigdelset(&set, SIGQUIT);
ret = sigismember(&set, SIGQUIT);
if (ret==0) printf("SIGQUIT not block\n");
if (ret==1) printf("SIGQUIT block\n");

return 0;
}

sigprocmask 函数使用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
// sigprocmask
#include <signal.h>

/* Prototype for the glibc wrapper function */
int sigprocmask(int how, const sigset_t *set, sigset_t *oldset);

// sigprocmask.c
#include <stdio.h>
#include <signal.h>
#include <unistd.h>
#include <stdlib.h>

int main() {
int num=0;
sigset_t set;
sigaddset(&set, SIGINT);
sigaddset(&set, SIGQUIT);
sigprocmask(SIG_BLOCK, &set, NULL);

while (1) {
num++;
sleep(1);
sigset_t pendingset;
sigemptyset(&pendingset);
sigpending(&pendingset);
for (int i=0; i<32; i++) {
if (sigismember(&pendingset, i)==1) {
printf("1");
}else{
printf("0");
}
}
printf("\n");
if (num==10) {
sigprocmask(SIG_UNBLOCK, &set, NULL);
}
}
return 0;
}

sigaction 信号捕捉函数

1
2
3
4
#include <signal.h>

int sigaction(int signum, const struct sigaction *act,
struct sigaction *oldact);
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
/* sigaction.c */
#include <stdio.h>
#include <signal.h>
#include <sys/time.h>
#include <stdlib.h>

/**
* alarm Handler
* @param num signal caught
*/
void myalarmHandler(int num) {
printf("catch signal:%d\n", num);
return;
}

int main() {
// signal(SIGALRM, SIG_IGN);
struct sigaction act;
act.sa_flags = 0;
act.sa_handler = myalarmHandler;
sigemptyset(&act.sa_mask);
sigaction(SIGALRM, &act, NULL );

struct itimerval new_value;
new_value.it_interval.tv_sec = 2;
new_value.it_interval.tv_usec = 0;
new_value.it_value.tv_sec = 3;
new_value.it_value.tv_usec = 0;

int ret = setitimer(ITIMER_REAL, &new_value, NULL);
printf("Itimer start.\n");
if (ret==-1) {
perror("setitimer");
exit(0);
}

while(1);
return 0;
}

SIGCHLD 信号

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
// sigchld.c
#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
#include <signal.h>
#include <sys/stat.h>
#include <wait.h>
#include <stdlib.h>

void myFun(int num) {
printf("caught signal: %d\n", num);
//TO DO
while(1) {
int ret = waitpid(-1, NULL, WNOHANG);
if (ret>0) printf("caught child %d\n", ret);
else if (ret==0) {
printf("caught no child process\n");
break;
}else{
break;
}
}

}

int main() {
//block signal before fork, in case child process died before signal registration.
sigset_t set;
sigemptyset(&set);
sigaddset(&set, SIGCHLD);
sigprocmask(SIG_BLOCK, &set, NULL);

pid_t pid;
for (int i=0; i<10; i++) {
pid = fork();
if (pid==0) break;
}

if (pid>0) {
struct sigaction act;
act.sa_flags = 0;
act.sa_handler = myFun;
sigemptyset(&act.sa_mask);
sigaction(SIGCHLD, &act, NULL);
sigprocmask(SIG_UNBLOCK, &set, NULL);
while(1) {
printf("I'm parent process, pid:%d\n", getpid());
sleep(2);
if (waitpid(-1, NULL, WNOHANG) == -1) {
printf("All child died.\n");
break;
}
}
}else{
printf("I'm child prcess, pid:%d, ppid:%d\n", getpid(), getppid());
}

return 0;
}

共享内存(1)

1
2
3
4
shmget()
shmat()
shmdt() // 进程终止时会自动完成这一步
shmctl() //删除共享内存(只时标记删除,当引用数为0时才正真删除)

共享内存(2)

1
2
ipcs -m
ipcrm
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// read_shm.c
#include <sys/ipc.h>
#include <sys/shm.h>
#include <stdio.h>

int main() {
int shmid = shmget(1000, 4096, IPC_CREAT);
printf("shmid:%d\n", shmid);
void *ptr = shmat(shmid, NULL, 0);
printf("%s\n", (char*)ptr);
printf("continue with pressing any char:\n");
getchar();
shmdt(ptr);
shmctl(shmid, IPC_RMID, NULL);
return 0;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// write_shm.c
#include <stdio.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <string.h>

int main() {
int shmid = shmget(1000, 4096, IPC_CREAT|0664);
printf("shmid:%d\n", shmid);
void *ptr = shmat(shmid, NULL, 0);
char *str = "helloworld";
memcpy(ptr, str, strlen(str)+1);
printf("continue with pressing any char:\n");
getchar();
shmdt(ptr);
shmctl(shmid, IPC_RMID, NULL);
return 0;
}

共享内存 vs. 内存映射
突然退出:共享内存还存在,内存映射消失
宕机:共享内存消失,内存映射的硬盘存在
生命周期:

守护进程(1)

1
2
echo $$
tty

会话:共享控制终端
前台、后台终端
PID, PPID,PGID,SID
首进程(bash)PID=SID
任意时刻只能有一个前台进程组

1
2
3
4
5
pid_t getpgrp(void);
pid_t getpgid(pid_t pid);
int setpgid (pid_t pid, pid_t pgid);
pid t getsid(pid_t pid);
pid t setsid (void);

Daemon Process
后台进程,通常独立于控制终端

守护进程(2)

  • 执行一个fork().之后父进程退出,子进程继续执行。
  • 子逬程调用setsid ()」开一个新会活。
  • 清除进程的umask以确保当守护进程创建文件和目录时拥有所需的权限。
  • 修改进程的当前工作目录,通常会改为根目录(1。
  • 关闭守护进程从其父进程继承而来的所有打开着的文件描述符。
  • 在关閉了文件描述行0、1、2之后,守程通常会才升/dev/nur」 井使用dup2()
  • 使所有这些描述符指向这个设备。
  • 核心业务逻辑
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    // daemon.c
    /*
    写一个守护进程,每隔2s获取一下系统时间,将这个时间写入到磁盘文件中。
    */

    #include <stdio.h>
    #include <sys/stat.h>
    #include <sys/types.h>
    #include <unistd.h>
    #include <fcntl.h>
    #include <sys/time.h>
    #include <signal.h>
    #include <time.h>
    #include <stdlib.h>
    #include <string.h>

    void work(int num) {
    // 捕捉到信号之后,获取系统时间,写入磁盘文件
    time_t tm = time(NULL);
    struct tm * loc = localtime(&tm);
    // char buf[1024];
    // sprintf(buf, "%d-%d-%d %d:%d:%d\n",loc->tm_year,loc->tm_mon
    // ,loc->tm_mday, loc->tm_hour, loc->tm_min, loc->tm_sec);
    // printf("%s\n", buf);
    char * str = asctime(loc);
    int fd = open("/Users/meqt/Documents/Xcode/Cpp/SoftWareCourse/Lesson28/time.txt", O_RDWR | O_CREAT | O_APPEND, 0664);
    write(fd ,str, strlen(str));
    close(fd);
    }

    int main() {
    // 1.创建子进程,退出父进程
    pid_t pid = fork();
    if(pid > 0) {
    exit(0);
    }
    // 2.将子进程重新创建一个会话
    setsid();
    // 3.设置掩码
    umask(022);
    // 4.更改工作目录
    chdir("/");
    // 5. 关闭、重定向文件描述符
    int fd = open("/dev/null", O_RDWR);
    dup2(fd, STDIN_FILENO);
    dup2(fd, STDOUT_FILENO);
    dup2(fd, STDERR_FILENO);

    // 6.业务逻辑
    // 捕捉定时信号
    struct sigaction act;
    act.sa_flags = 0;
    act.sa_handler = work;
    sigemptyset(&act.sa_mask);
    sigaction(SIGALRM, &act, NULL);

    struct itimerval val;
    val.it_value.tv_sec = 2;
    val.it_value.tv_usec = 0;
    val.it_interval.tv_sec = 2;
    val.it_interval.tv_usec = 0;

    // 创建定时器
    setitimer(ITIMER_REAL, &val, NULL);

    int i=0;
    // 不让进程结束
    for (int i=0; i<10; i++) {
    sleep(1);
    }
    return 0;
    }
  • Post title:C/C++ WebServer 2 Linux 多进程开发
  • Post author:Meqt
  • Create time:2022-12-17 17:40:08
  • Post link:https://meqtmac.github.io/2022/12/17/WebServerNote/webserver-note2/
  • Copyright Notice:All articles in this blog are licensed under BY-NC-SA unless stating additionally.