本文包含进程的介绍和使用,以及进程间的通信方式。
进程
进程的概念
进程是程序的一次执行过程,进程是一个正在执行的任务,进程是分配资源的最小单位。每一个进程都会分配自己的0-3G的内存空间,0-3G的内存空间有多份,而3-4G内核空间只有一份。在这0-3G内存空间中堆区、栈区、静态区(缓冲区,文件描述符)。
进程其实是内核创建的,每个进程在内核空间都对应的是一个 task_struct(PCB)
的结构体。正在运行的进程会被放到一个运行队列中,随着时间片轮询依次来执行进程。一个进程的崩溃不会影响另外一个进程的执行,进程的安全性高。
进程和程序的区别
程序:程序是静态的,没有生命周期的概念,它是有序的指令的集合,在硬盘上存储着
进程:进程是程序的一次执行过程,它是由生命周期的,随着程序的执行而运行,随着程序的终止而结束,在内存上存储着。可以分配自己的0-3G的内存空间
进程的组成
- 进程控制块
PCB task_struct
进程的标识符PID,进程运行的状态,进程所属的用户uid,内存空间…
- 文本段
存放可执行程序本身
- 数据段
存放程序运行时候产生的数据,比如int a;a就在数据段存放
进程的种类
交互进程 :交互进程是由shell维护的,通过shell和用户进行交互,例如文本编辑器就是一个交互进程。
批处理进程:批处理进程的优先级比较低,通常情况下批处理进程都会被放到队列中执行,例如gcc编译程序的过程就是批处理进程。
守护进程 :守护进程是一个后台运行的进程,随着系统的启动而启动,随着系统的终止而终止,它会脱离终端执行,例如windows上的各种服务。
PID
PID process id
: 进程号,在linux系统上进程都会被分配一个ID,这个ID就是进程的标号。在linux系统上所有的进程都可以在 /proc
目录下查看。PPID
是进程的父进程号。在一个系统上可以通过如下命令查看能创建的最大进程的个数:
1
| cat /proc/sys/kernel/pid_max
|
特殊PID的进程
0号进程 idle
: 在linux系统启动的时候最先运行的进程就是0号进程,0号进程又叫空闲进程。如果系统上没有其他进程执行那么0号进程就执行。0号进程是1号进程和2号进程的父进程
1号进程 init
: init进程是由0号进程创建得到的,它的主要工作是系统的初始化。当初始化工作执行完之后,它主要负责回收孤儿进程的资源。
2号进程 kthreadd
: kthreadd是有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
| ps -ef
ps -ajx
sudo apt-get install htop top htop
kill -l kill -信号号 PID
pidof FILE_NAME killall FILE_NAME cat /proc/sys/kernel/pid_max
|
进程的状态
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| 1. 进程的状态 * D 不可中断的等待态(sleep,不可被信号打断) * R 运行状态 * S 可中断的等待态(sleep,可被信号打断) * T 停止状态 * X 死亡状态 * Z 僵尸态 * I 空闲态
2. 进程的附加态 * < 高优先级进程 * N 低优先级进程 * L 锁在内存上 * s 会话组组长 * l 包含多线程 * + 前台进程
|
孤儿进程 :一个进程的父进程死亡,此时当前的进程就是孤儿进程,孤儿进程被init收养
僵尸态进程 :如果一个进程结束,父进程没有为它收尸,此时当前的进程就是僵尸进程。
进程状态切换实例
1 2 3 4 5 6 7 8
| #include <head.h> int main(int argc,const char * argv[]) { while(1){ sleep(1); } return 0; }
|
1 2 3 4 5 6 7 8 9 10 11 12
| ./a.out ps -ajx | grep a.out
ps -ajx | grep a.out
jobs -l bg 1 ps -ajx | grep a.out
fg 1 ps -ajx | grep a.out
|
进程的创建及特点
如何创建进行
进程的创建是拷贝父进程得到的,通过拷贝过程更容易得到子进程,并且能够标识进程的父子状态。
创建进程的API
1 2 3 4 5 6 7 8 9
| #include <sys/types.h> #include <unistd.h>
pid_t fork(void);
|
创建进程的实例
创建一个子进程,不关注返回值
1 2 3 4 5 6 7 8 9 10
| #include <head.h>
int main(int argc, const char* argv[]) { fork(); while (1);
return 0; }
|
创建进程的实例
创建多个子进程,不关注返回值
1 2 3 4 5 6 7 8 9 10 11
| #include <head.h>
int main(int argc, const char* argv[]) { for (int i = 0; i < 3; i++) { fork(); } while (1);
return 0; }
|
按照上述的写法:fork n
次就产生了 2^n
个进程
可以分次总体的理解上述程序的执行过程
也可以按照父子关系理解上述程序的执行过程
fork
和 缓冲区 结合问题
1 2 3 4 5 6 7 8 9 10 11
| #include <head.h>
int main(int argc, const char* argv[]) { for (int i = 0; i < 2; i++) { fork(); printf("-"); }
return 0; }
|
上述程序会打印8个 -
。
原因是上述程序printf没有刷新缓冲区,所以在fork进程的时候,会将父进程缓冲区的内容也fork过来,所以最终打印了8个’-‘
关注 fork
返回值
fork
的返回值是区分父子进程的关键
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| #include <head.h>
int main(int argc,const char * argv[]) { pid_t pid;
pid = fork(); if(pid == -1){ PRINT_ERR("fork error"); }else if(pid == 0){ }else{ }
return 0; }
|
父子进程执行先后顺序
父子进程执行没有先后顺序,时间片轮询,上下文切换。
父子进程内存空间问题
在fork前父进程中所有的变量多被拷贝到了子进程中,在子进程中打印的变量的虚拟地址和父进程一样,但是两者在物理地址上一定是不同的,所以在子进程内修改变量的值,父进程中变量不会改变。父子进程内存空间相互独立。
写时拷贝 cow(copy on write)
在使用fork产生子进程的时候,此时父子进程共用同一块物理内存,但是在子进程或者父进程中尝试修改a变量的时候,此时就会分配一块新的物理内存,此时父子进程a变量虚拟内存相同,但是对应的物理内存不同。
多进程练习
使用两个进程拷贝同一个文件,父进程拷贝文件的前一半,子进程拷贝文件的后一半。
1
| ./a.out srcfile destfile
|
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 73 74 75 76 77 78 79 80 81 82 83
| #include <head.h> int get_file_len(const char* file) { int fd, len; if ((fd = open(file, O_RDONLY)) == -1) PRINT_ERR("open error"); len = lseek(fd, 0, SEEK_END); close(fd); return len; } int init_src_file(const char* file) { int fd; if ((fd = open(file, O_WRONLY | O_CREAT | O_TRUNC, 0666)) == -1) PRINT_ERR("open error");
close(fd); return 0; } int copy_file(const char* src, const char* dest, int start, int len) { char s[20]; int fd1, fd2; int ret, count = 0; if ((fd1 = open(src, O_RDONLY)) == -1) PRINT_ERR("open src error"); if ((fd2 = open(dest, O_WRONLY)) == -1) PRINT_ERR("open dest error"); lseek(fd1, start, SEEK_SET); lseek(fd2, start, SEEK_SET); while (count < len) { ret = read(fd1, s, sizeof(s)); count += ret; write(fd2, s, ret); } close(fd1); close(fd2); return 0; }
int main(int argc, const char* argv[]) { int len; pid_t pid; if (argc != 3) { fprintf(stderr, "input error,try again\n"); fprintf(stderr, "usage:./a.out srcfile destfile\n"); return -1; } len = get_file_len(argv[1]);
init_src_file(argv[2]);
if ((pid = fork()) == -1) { PRINT_ERR("fork error"); } else if (pid == 0) { copy_file(argv[1], argv[2], len / 2, (len - len / 2)); } else { copy_file(argv[1], argv[2], 0, len / 2); wait(NULL); }
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
| #include <head.h>
int main(int argc, const char* argv[]) { pid_t pid; int fd;
if((fd = open("./hello.txt",O_RDWR))==-1) PRINT_ERR("open error");
pid = fork(); if (pid == -1) { PRINT_ERR("fork error"); } else if (pid == 0) { lseek(fd,5,SEEK_SET); } else { sleep(1); char ch; read(fd,&ch,1); printf("ch = %c\n",ch); } close(fd); return 0; }
|
进程相关的API接口
getpid
getppid
函数
1 2 3 4 5 6 7 8
| #include <sys/types.h> #include <unistd.h>
pid_t getpid(void);
pid_t getppid(void);
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| #include <head.h>
int main(int argc, const char* argv[]) { pid_t pid; pid = fork(); if (pid == -1) { PRINT_ERR("fork error"); } else if (pid == 0) { printf("child:pid = %d,ppid = %d\n",getpid(),getppid()); } else { printf("parent:pid = %d,ppid = %d,cpid = %d\n",getpid(),getppid(),pid); } return 0; }
|
exit
_exit
函数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| void exit(int status);
void _exit(int status);
|
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
| #include <head.h> void func(void) { printf("111111111\n"); printf("2222222222"); exit(EXIT_SUCCESS); printf("3333333333\n"); } int main(int argc, const char* argv[]) { pid_t pid; pid = fork(); if (pid == -1) { PRINT_ERR("fork error"); } else if (pid == 0) { func(); while(1); } else { while (1); } 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
| #include <head.h> void func(void) { printf("111111111\n"); printf("2222222222"); _exit(EXIT_SUCCESS); printf("3333333333\n"); } int main(int argc, const char* argv[]) { pid_t pid;
pid = fork(); if (pid == -1) { PRINT_ERR("fork error"); } else if (pid == 0) { func(); while(1); } else { while (1); } return 0; }
|
wait
waitpid
函数
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
| #include <sys/types.h> #include <sys/wait.h>
pid_t wait(int *wstatus);
wait(NULL);
int wstatus; wait(&wstatus);
pid_t waitpid(pid_t pid, int *wstatus, int options);
|
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
| #include <head.h> void func(void) { printf("111111111\n"); printf("2222222222"); _exit(34); printf("3333333333\n"); } int main(int argc, const char* argv[]) { pid_t pid;
pid = fork(); if (pid == -1) { PRINT_ERR("fork error"); } else if (pid == 0) { func(); while(1); } else { int wstatus; pid_t pid1; if((pid1 = wait(&wstatus))==-1) PRINT_ERR("wait error");
printf("pid = %d,pid1 = %d\n",pid,pid1); if(WIFEXITED(wstatus)){ printf("status = %d\n",WEXITSTATUS(wstatus)); } if(WIFSIGNALED(wstatus)){ printf("signo = %d\n", WTERMSIG(wstatus)); } while(1); } return 0; }
|
waitpid函数实例
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
| #include <head.h> void func(void) { printf("111111111\n"); printf("2222222222"); sleep(1); printf("我是第一个子进程\n"); _exit(34); printf("3333333333\n"); } int main(int argc, const char* argv[]) { pid_t pid;
pid = fork(); if (pid == -1) { PRINT_ERR("fork error"); } else if (pid == 0) { func(); while (1) ; } else { pid_t pid1; if ((pid1 = fork()) == -1) { PRINT_ERR("fork error"); } else if (pid1 == 0) { sleep(1); printf("我是第二个子进程\n"); exit(0); }
int wstatus; pid_t pid2; if ((pid2 = waitpid(-1, &wstatus, WNOHANG)) == -1) PRINT_ERR("wait error");
printf("pid = %d,pid2 = %d\n", pid, pid2); if (WIFEXITED(wstatus)) { printf("status = %d\n", WEXITSTATUS(wstatus)); } if (WIFSIGNALED(wstatus)) { printf("signo = %d\n", WTERMSIG(wstatus)); } while (1); } return 0; }
|
进程间的通信
进程间通信方式简介
在linux系统上常用的进程间通信方式有如下7种:
- 传统进程间通信
- System V IPC进程间通信
- BSD(伯克利分校)基于网络的进程间通信
无名管道
无名管道通信原理
如果A和B进程想要通过无名管道通信,那就必须在内核空间创建一个无名管道 (64K), A和B进程必须是亲缘关系的进程,A进程向管道的一端写数据,B进程可以从管道的另外一端读数据。在A进程和B进程进行数据传输的时候是不允许使用 lseek
函数的。无名管道是 半双工 的通信方式。
如果A进程一直向管道中写数据写满 64K
的时候A进程阻塞,直到B进程读一部分数据之后A才能继续写。
如果B进程在读数据的时候,无名管道是空的,B进程阻塞。
无名管道的API
1 2 3 4 5 6 7 8 9
| #include <unistd.h>
int pipe(int pipefd[2]);
|
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
| #include <head.h>
int main(int argc, const char* argv[]) { pid_t pid; int pipefd[2]; char s[128] = { 0 }; if (pipe(pipefd)) PRINT_ERR("pipe error");
if ((pid = fork()) == -1) { PRINT_ERR("fork error"); } else if (pid == 0) { close(pipefd[1]); while (1) { memset(s,0,sizeof(s)); read(pipefd[0], s, sizeof(s)); if (strcmp(s, "quit") == 0) break; printf("%s\n", s); } close(pipefd[0]); exit(EXIT_SUCCESS); } else { close(pipefd[0]); while (1) { fgets(s, sizeof(s), stdin); if (s[strlen(s) - 1] == '\n') s[strlen(s) - 1] = '\0'; write(pipefd[1], s, strlen(s)); if (strcmp(s, "quit") == 0) break; } close(pipefd[1]); wait(NULL); } return 0; }
|
无名管道通信特点
读端存在写管道:有多少写多少,直到写满(64K)为止,写阻塞
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| #include <head.h>
int main(int argc, const char* argv[]) { int pipefd[2]; char s[128] = { 0 };
if (pipe(pipefd)) PRINT_ERR("pipe error"); char ch='a'; for(int i=0;i<65537;i++){ write(pipefd[1],&ch,1); } printf("11111111111111111111111111\n"); return 0; }
|
读端不存在写管道:管道破裂,进程收到SIGPIPE信号,进程退出
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| #include <head.h>
int main(int argc, const char* argv[]) { int pipefd[2]; char s[128] = { 0 };
if (pipe(pipefd)) PRINT_ERR("pipe error"); close(pipefd[0]);
char ch='a'; write(pipefd[1],&ch,1); while(1);
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
| #include <head.h>
int main(int argc, const char* argv[]) { int pipefd[2]; char s[128] = "i am test pipe func...\n";
if (pipe(pipefd)) PRINT_ERR("pipe error"); write(pipefd[1],s,strlen(s));
close(pipefd[1]); char ch; while(1){ read(pipefd[0],&ch,1); printf("ch = %c\n",ch); usleep(100000); } return 0; }
|
总结:
- 读端存在写管道:有多少写多少,直到写满(64K)为止,写阻塞
- 读端不存在写管道:管道破裂,进程收到SIGPIPE信号,进程退出
- 写端存在读管道:管道中有多少字节的数据就能读多少数据,如果没有数据的时候,读阻塞
- 写端不存在读管道:管道中有多少字节的数据就能读多少数据,如果没有数据的时候,读立即返回
有名管道
有名管道通信原理
有名管道可以实现任意进程间的通信,有名管道的大小也是 64K
,有名管道也是不支持 lseek
,有名管道也是 半双工 的通信方式。有名管道创建之后会在 用户空间产生一个管道文件,这个管道文件是在内存上存储的。
如果A和B两个进程想要通过有名管道通信,就打开管道文件,向管道中写向管道中读就可
有名管道的API
1 2 3 4 5 6
| int mkfifo(const char *pathname, mode_t mode);
|
01mkfifo.c
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| #include <head.h> #define FIFO_NAME "./myfifo" int main(int argc,const char * argv[]) { if(mkfifo(FIFO_NAME,0666)) PRINT_ERR("mkfifo error");
getchar();
char s[50] = {0}; snprintf(s,sizeof(s),"rm -rf %s",FIFO_NAME); system(s); return 0; }
|
02write.c
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
| #include <head.h> #define FIFO_NAME "./myfifo" int main(int argc, const char* argv[]) { int fd; char s[128] = { 0 }; if ((fd = open(FIFO_NAME, O_WRONLY)) == -1) PRINT_ERR("open error");
while (1) { printf("input > "); fgets(s, sizeof(s), stdin); if (s[strlen(s) - 1] == '\n') s[strlen(s) - 1] = '\0'; write(fd, s, strlen(s)); if (strcmp(s, "quit") == 0) break; } close(fd);
return 0; }
|
03read.c
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| #include <head.h> #define FIFO_NAME "./myfifo" int main(int argc, const char* argv[]) { int fd; char s[128] = { 0 }; if ((fd = open(FIFO_NAME, O_RDONLY)) == -1) PRINT_ERR("open error");
while (1) { memset(s, 0, sizeof(s)); read(fd, s, sizeof(s)); printf("s = %s\n", s); if (strcmp(s, "quit") == 0) break; }
close(fd);
return 0; }
|
有名管道的练习
使用有名管道传输文件,
A进程读文件,将文件中的内容写入到管道中
B进程读取管道,将管道中的数据写入到新文件中。
01mkfifo.c
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| #include <head.h> #define FIFO_NAME "./myfifo" int main(int argc,const char * argv[]) { if(mkfifo(FIFO_NAME,0666)) PRINT_ERR("mkfifo error");
getchar();
char s[50] = {0}; snprintf(s,sizeof(s),"rm -rf %s",FIFO_NAME); system(s); return 0; }
|
02write.c
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
| #include <head.h> #define FIFO_NAME "./myfifo" int main(int argc, const char* argv[]) { int fd1, fd2, ret; char s[128] = { 0 }; if (argc != 2) { fprintf(stderr, "input error,try again\n"); fprintf(stderr, "usgage: ./a.out srcfile\n"); return -1; } if ((fd1 = open(argv[1], O_RDONLY)) == -1) PRINT_ERR("open error"); if ((fd2 = open(FIFO_NAME, O_WRONLY)) == -1) PRINT_ERR("open error");
while ((ret = read(fd1, s, sizeof(s))) > 0) { write(fd2,s,ret); }
close(fd1); close(fd2);
return 0; }
|
03read.c
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
| #include <head.h> #define FIFO_NAME "./myfifo" int main(int argc, const char* argv[]) { int fd1, fd2, ret; char s[128] = { 0 }; if (argc != 2) { fprintf(stderr, "input error,try again\n"); fprintf(stderr, "usgage: ./a.out destfile\n"); return -1; } if ((fd1 = open(argv[1], O_WRONLY|O_CREAT|O_TRUNC,0666)) == -1) PRINT_ERR("open error"); if ((fd2 = open(FIFO_NAME, O_RDONLY)) == -1) PRINT_ERR("open error");
while ((ret = read(fd2, s, sizeof(s))) > 0) { write(fd1,s,ret); }
close(fd1); close(fd2);
return 0; }
|
有名管道读写的特点
读端存在写管道:有多少写多少,直到写满为止(64K),写阻塞
读端没有打开过,写管道:写端在open位置阻塞
读端先打开后关闭,写管道:管道破裂,收到SIGPIPE信号,进程结束
写端存在读管道:有多少读多少,没数据读的时候读阻塞
写端没有打开过,读管道:读端在open的位置阻塞
写端先打开后关闭,读管道:有多少读多少,没数据的时候读立即返回(读返回值是0)
信号
信号简介
用户可以通过 kill
命令给进程发信号,操作系统也可以给进程发送信号。进程对信号的响应方式有三种:忽略,默认,捕捉
发信号的命令
信号的查看方式
常用的信号
信号名 |
默认操作 |
说明 |
SIGHUP |
终止 |
该信号在用户终端关闭时产生,通常是发给和该终端关联的会话内的所有进程 |
SIGINT |
终止 |
该信号在用户键入 INTR 字符 ctrl + c 时产生,内核发送此信号发送到当前终端的所有前台进程 |
SIGQUIT |
终止 |
该信号和 SIGINT 类似,但由 UIT 字符 Ctrl + \ |
SIGILL |
终止 |
该信号在一个进程企图执行一条非法指令时产生 |
SIGSEV |
终止 |
该信号在非法访问内存时产生,如野指针、缓冲区溢出 |
SIGPIPR |
终止 |
当进程往一个没有读端的管道中写入时产生,代表 管道破裂 |
SIGKILL |
终止 |
该信号用于立即结束程序的运行,不能被捕捉或忽略 |
SIGSTOP |
暂停进程 |
该信号用于立即暂停程序的运行,不能被捕捉或忽略 |
SIGTSTP |
暂停进程 |
该信号用于暂停进程,在用户键入 SUSP 字符 Ctrl + z 时产生 |
SIGCONT |
继续运行 |
该信号用于继续运行进程 |
SIGALRM |
终止 |
该信号在调用 alarm 函数设置的定时器超时时产生 |
SIGUSR1/2 |
终止 |
该信号由用户使用 |
SIGCHLD
: 当子进程退出的时候,父进程会收到这个信号
注:在所有的信号中,只有SIGKILL/SIGSTOP两个信号不能被捕捉,也不能被忽略。只能执行默认的动作。
signal
函数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| #include <signal.h>
typedef void (*sighandler_t)(int);
sighandler_t signal(int signum, sighandler_t handler);
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| #include <head.h> void signal_handle(int signo) { if(signo == SIGINT){ printf("我收到了一个ctrl+c信号\n"); } } int main(int argc, const char* argv[]) {
if (SIG_ERR == signal(SIGINT, signal_handle)) PRINT_ERR("signal error");
while (1); return 0; }
|
练习
使用signal捕捉管道破裂信号
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
| #include <head.h> void handle(int signo) { switch (signo) { case SIGPIPE: printf("我捕捉到了一个管道破裂信号\n"); break; case SIGINT: printf("我捕捉到了一个ctrl+c信号\n"); break; } } int main(int argc, const char* argv[]) { int pipefd[2]; char s[128] = { 0 };
if ((SIG_ERR == signal(SIGINT, handle))) PRINT_ERR("signal error");
if ((SIG_ERR == signal(SIGPIPE, handle))) PRINT_ERR("signal error");
if (pipe(pipefd)) PRINT_ERR("pipe error");
close(pipefd[0]);
char ch = 'a'; while (1) { write(pipefd[1], &ch, 1); sleep(1); }
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
| #include <head.h> char cmd[128] = {0}; void handle(int signo) { waitpid(-1,NULL,WNOHANG); printf("我是父进程,我以非阻塞方式回收掉了子进程的资源\n"); snprintf(cmd,sizeof(cmd),"kill -%d %d",SIGUSR1,getpid()); system(cmd); } int main(int argc, const char* argv[]) { pid_t pid;
if ((pid = fork()) == -1) { PRINT_ERR("fork error"); }else if(pid == 0){ sleep(7); printf("我是子进程,我执行了7s,我现在要退出...\n"); exit(EXIT_SUCCESS); }else{ if(SIG_ERR == signal(SIGCHLD,handle)) PRINT_ERR("signal error"); while(1); } 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
| int raise(int sig);
int kill(pid_t pid, int sig);
unsigned int alarm(unsigned int seconds);
|
raise
/ kill
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 <head.h>
void handle(int signo) { waitpid(-1,NULL,WNOHANG); printf("我是父进程,我以非阻塞方式回收掉了子进程的资源\n"); kill(getpid(),SIGUSR1); } int main(int argc, const char* argv[]) { pid_t pid;
if ((pid = fork()) == -1) { PRINT_ERR("fork error"); }else if(pid == 0){ sleep(7); printf("我是子进程,我执行了7s,我现在要退出...\n"); exit(EXIT_SUCCESS); }else{ if(SIG_ERR == signal(SIGCHLD,handle)) PRINT_ERR("signal error"); while(1); } return 0; }
|
alarm
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| #include <head.h>
void handle(int signo) { printf("我收到了闹钟信号\n"); } int main(int argc,const char * argv[]) { if(SIG_ERR == signal(SIGALRM,handle)) PRINT_ERR("signal error");
printf("第一次 = %d\n",alarm(5)); sleep(2); printf("第二次 = %d\n",alarm(5)); while(1); return 0; }
|
alarm
2
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| #include <head.h> void handle(int signo) { printf("系统自动出牌了\n"); alarm(4); } int main(int argc,const char * argv[]) { if(SIG_ERR == signal(SIGALRM,handle)) PRINT_ERR("signal error");
alarm(4); char ch; while(1){ ch = getchar(); getchar(); printf("用户出牌 = %c\n",ch); alarm(4); } return 0; }
|
IPC进程间通信相关命令
1 2 3 4 5 6
| ipcs -q ipcs -m ipcs -s ipcrm -q msqid ipcrm -m shmid ipcrm -s semid
|
IPC进程间通信键值获取及组成
在使用IPC进程间通信的时候,首先需要获取一个key,只有当两个进程拿到相同的键的之后才能找到同一个IPC。
1 2 3 4 5 6 7 8 9
| #include <sys/types.h> #include <sys/ipc.h>
key_t ftok(const char *pathname, int proj_id);
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| #include <head.h>
int main(int argc, const char* argv[]) { key_t key; struct stat st; if ((key = ftok("/home/linux", 't')) == -1) PRINT_ERR("ftok error");
printf("key = %#x\n", key);
if (stat("/home/linux", &st)) PRINT_ERR("stat error");
printf("inode = %#lx,devno = %#lx,proj_id = %#x\n",st.st_ino,st.st_dev,'t'); return 0; }
|
消息队列
消息队列通信原理
如果想要使用消息队列实现进程间通信,就必须在内核空间创建出来消息队列,消息队列默认大小是 16384(16K)
,当创建好消息队列之后A进程可以向消息队列中发消息,消息的格式是 类型 + 正文
。当消息队列满的时候A进程如果还想往消息队列中发消息A进程休眠。B进程可以通过消息的类型从消息队列中取消息,取出的消息从队列中移除。
如果B进程想要获取的消息类型在队列中不存在B进程休眠等。
A进程向消息队列中发消息的时候可以采用:阻塞,非阻塞方式
B进程从消息队列中收消息的时候可以采用:阻塞,非阻塞方式
msgget
msgsnd
msgrcv
msgctl
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
| #include <sys/ipc.h> #include <sys/msg.h> int msgget(key_t key, int msgflg);
int msgsnd(int msqid, const void *msgp, size_t msgsz, int msgflg);
ssize_t msgrcv(int msqid, void *msgp, size_t msgsz, long msgtyp,int msgflg);
|
01snd.c
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
| #include <head.h> typedef struct { long mtype; char name[19]; char sex; int age; } msg_t;
#define MSGSIZE (sizeof(msg_t)-sizeof(long))
int main(int argc, const char* argv[]) { key_t key; int msgqid; if ((key = ftok("/home/linux/", 'p')) == -1) PRINT_ERR("ftok error");
if ((msgqid = msgget(key, IPC_CREAT | 0666)) == -1) PRINT_ERR("msgget error");
int ret; msg_t msg; while (1) { retry: printf("input (type name sex age) > "); ret = scanf("%ld %s %c %d", &msg.mtype, msg.name, &msg.sex, &msg.age); if (ret != 4) { printf("input error,try again\n"); while (getchar() != '\n'); goto retry; }
if(msgsnd(msgqid, &msg,MSGSIZE, 0)) PRINT_ERR("msgsnd error"); if(msg.mtype == 1000) break; }
return 0; }
|
02rcv.c
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
| #include <head.h> typedef struct { long mtype; char name[19]; char sex; int age; } msg_t;
#define MSGSIZE (sizeof(msg_t)-sizeof(long))
int main(int argc, const char* argv[]) { key_t key; int msgqid; if ((key = ftok("/home/linux/", 'p')) == -1) PRINT_ERR("ftok error");
if((msgqid = msgget(key,IPC_CREAT|0666))==-1) PRINT_ERR("msgget error"); long type; msg_t msg; while(1){ printf("input (type) > "); scanf("%ld",&type);
if(msgrcv(msgqid,&msg,MSGSIZE,type,0)==-1) PRINT_ERR("msgrcv error"); printf("type=%ld,name=%s,sex=%c,age=%d\n",msg.mtype,msg.name,msg.sex,msg.age); if(msg.mtype == 1000) break; } if(msgctl(msgqid,IPC_RMID,NULL)) PRINT_ERR("msgctl error"); return 0; }
|
msgctl
函数详解
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
| int msgctl(int msqid, int cmd, struct msqid_ds *buf);
msgctl(msqid,IPC_RMID,NULL);
struct msqid_ds msqds; msgctl(msqid, IPC_STAT, &msqds);
struct msqid_ds { struct ipc_perm msg_perm; __time_t msg_stime; __time_t msg_rtime; __syscall_ulong_t __msg_cbytes; msgqnum_t msg_qnum; msglen_t msg_qbytes; __pid_t msg_lspid; __pid_t msg_lrpid; }; struct ipc_perm { __key_t __key; __uid_t uid; __gid_t gid; __uid_t cuid; __gid_t cgid; unsigned short int mode; };
|
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
| #include <head.h> typedef struct { long mtype; char name[19]; char sex; int age; } msg_t;
#define MSGSIZE (sizeof(msg_t) - sizeof(long))
int main(int argc, const char* argv[]) { key_t key; int msgqid; if ((key = ftok("/home/linux/", 'p')) == -1) PRINT_ERR("ftok error");
if ((msgqid = msgget(key, IPC_CREAT | 0666)) == -1) PRINT_ERR("msgget error");
int ret; msg_t msg; while (1) { retry: printf("input (type name sex age) > "); ret = scanf("%ld %s %c %d", &msg.mtype, msg.name, &msg.sex, &msg.age); if (ret != 4) { printf("input error,try again\n"); while (getchar() != '\n') ; goto retry; }
if (msgsnd(msgqid, &msg, MSGSIZE, 0)) PRINT_ERR("msgsnd error");
if (msg.mtype == 1000) break; }
struct msqid_ds msqds; if (msgctl(msgqid, IPC_STAT, &msqds)) PRINT_ERR("msgctl error");
printf("key=%#x,uid=%d,gid=%d,mode=%#o,nq=%ld,nb=%ld,mb=%ld\n", msqds.msg_perm.__key, msqds.msg_perm.uid, msqds.msg_perm.gid, msqds.msg_perm.mode, msqds.msg_qnum, msqds.__msg_cbytes, msqds.msg_qbytes);
msqds.msg_qbytes = 32768; if (msgctl(msgqid, IPC_SET, &msqds)) PRINT_ERR("msgctl error");
memset(&msqds, 0, sizeof(msqds)); if (msgctl(msgqid, IPC_STAT, &msqds)) PRINT_ERR("msgctl error");
printf("key=%#x,uid=%d,gid=%d,mode=%#o,nq=%ld,nb=%ld,mb=%ld\n", msqds.msg_perm.__key, msqds.msg_perm.uid, msqds.msg_perm.gid, msqds.msg_perm.mode, msqds.msg_qnum, msqds.__msg_cbytes, msqds.msg_qbytes);
if (msgctl(msgqid, IPC_RMID, NULL)) PRINT_ERR("msgctl error"); return 0; }
|
共享内存
共享内存的工作原理
共享内存是所有进程间通信方式中效率最高的一个,因为当创建共享内存之后,需要通信的A和B进程可以直接操作这块物理内存空间 ,省去了向内核拷贝数据的过程。共享内存的大小是 4K
的整数倍。
shmget
shmat
shmdt
shmctl
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
| #include <sys/shm.h> int shmget(key_t key, size_t size, int shmflg);
void *shmat(int shmid, const void *shmaddr, int shmflg);
int shmdt(const void *shmaddr);
int shmctl(int shmid, int cmd, struct shmid_ds *buf);
|
01write.c
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
| #include <head.h> #define SHMSIZE (4096) int main(int argc, const char* argv[]) { key_t key; int shmid; char* waddr; if ((key = ftok("/home", 'g')) == -1) PRINT_ERR("ftok error"); if ((shmid = shmget(key, SHMSIZE, IPC_CREAT | 0666)) == -1) PRINT_ERR("shmget error"); if ((waddr = shmat(shmid, NULL, 0)) == (void*)-1) PRINT_ERR("shmat error"); while (1) { printf("input > "); fgets(waddr, SHMSIZE, stdin); if (waddr[strlen(waddr) - 1] == '\n') waddr[strlen(waddr) - 1] = '\0'; if (strcmp(waddr, "quit") == 0) break; } if(shmdt(waddr)) PRINT_ERR("shmdt error"); return 0; }
|
02read.c
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
| #include <head.h> #define SHMSIZE (4096) int main(int argc, const char* argv[]) { key_t key; int shmid; char* raddr; if ((key = ftok("/home", 'g')) == -1) PRINT_ERR("ftok error"); if ((shmid = shmget(key, SHMSIZE, IPC_CREAT | 0666)) == -1) PRINT_ERR("shmget error"); if ((raddr = shmat(shmid, NULL, 0)) == (void*)-1) PRINT_ERR("shmat error"); while (1) { getchar(); printf("read:%s\n", raddr); if (strncmp(raddr, "quit", 4) == 0) break; } if (shmdt(raddr)) PRINT_ERR("shmdt error"); if (shmctl(shmid, IPC_RMID, NULL)) PRINT_ERR("shmctl error"); return 0; }
|
shmctl
函数详解
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
| int shmctl(int shmid, int cmd, struct shmid_ds *buf);
shmctl(shmid,IPC_RMID,NULL);
struct shmid_ds buf; shmctl(shmid,IPC_STAT,&buf);
struct shmid_ds { struct ipc_perm shm_perm; size_t shm_segsz; __time_t shm_atime; __time_t shm_dtime; __time_t shm_ctime; __pid_t shm_cpid; __pid_t shm_lpid; shmatt_t shm_nattch; }; struct ipc_perm { __key_t __key; __uid_t uid; __gid_t gid; __uid_t cuid; __gid_t cgid; unsigned short int mode; };
|
信号灯集
信号灯集工作原理
信号量(信号灯集)是实现 进程同步 的机制,在一个信号灯集中可以有很多个信号灯。在信号灯集内信号灯相互独立,每个灯的值的改变不会影响其他的信号灯,信号灯的值一般设置为二值量( 1
或者 0
,1
代表有资源,0
代表没有资源)。
semget
semctl
semop
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
| #include <sys/sem.h>
int semget(key_t key, int nsems, int semflg);
int semctl(int semid, int semnum, int cmd, ...);
union semun sem = { .val = 1, } semctl(semid,0,SETVAL,sem); union semun sem = { .val = 0, } semctl(semid,1,SETVAL,sem);
val = semctl(semid,0,GETVAL);
struct semid_ds buf; union semun sem = { .buf = &buf, } semctl(semid,0,IPC_STAT,sem);
semctl(semid,0,IPC_RMID);
int semop(int semid, struct sembuf *sops, size_t nsops);
|
01write.c
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 73 74 75 76 77 78 79 80 81
| #include <head.h> #define SHMSIZE (4096) union semun { int val; struct semid_ds* buf; }; int mysem_init(int semid, int which, int value) { union semun sem = { .val = value, }; if (semctl(semid, which, SETVAL, sem) == -1) PRINT_ERR("semctl error");
return 0; } int P(int semid, int which) { struct sembuf op = { .sem_num = which, .sem_op = -1, .sem_flg = 0, }; if (semop(semid, &op, 1)) PRINT_ERR("semop P error"); return 0; } int V(int semid, int which) { struct sembuf op = { .sem_num = which, .sem_op = 1, .sem_flg = 0, }; if (semop(semid, &op, 1)) PRINT_ERR("semop V error"); return 0; } int main(int argc, const char* argv[]) { key_t key; int shmid, semid; char* waddr; if ((key = ftok("/home", 'g')) == -1) PRINT_ERR("ftok error"); if ((shmid = shmget(key, SHMSIZE, IPC_CREAT | 0666)) == -1) PRINT_ERR("shmget error"); if ((waddr = shmat(shmid, NULL, 0)) == (void*)-1) PRINT_ERR("shmat error");
if ((semid = semget(key, 2, IPC_CREAT | IPC_EXCL | 0666)) == -1) { if (errno = EEXIST) { semid = semget(key, 2, IPC_CREAT|0666); } else { PRINT_ERR("semget error"); } } else { mysem_init(semid, 0, 1); mysem_init(semid, 1, 0); }
while (1) { P(semid, 0); printf("input > "); fgets(waddr, SHMSIZE, stdin); if (waddr[strlen(waddr) - 1] == '\n') waddr[strlen(waddr) - 1] = '\0'; V(semid, 1); if (strcmp(waddr, "quit") == 0) break; } if (shmdt(waddr)) PRINT_ERR("shmdt error"); return 0; }
|
02read.c
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 73 74 75 76 77 78 79 80 81 82 83 84
| #include <head.h> #define SHMSIZE (4096) union semun { int val; struct semid_ds* buf; }; int mysem_init(int semid, int which, int value) { union semun sem = { .val = value, }; if (semctl(semid, which, SETVAL, sem) == -1) PRINT_ERR("semctl error");
return 0; } int P(int semid, int which) { struct sembuf op = { .sem_num = which, .sem_op = -1, .sem_flg = 0, }; if (semop(semid, &op, 1)) PRINT_ERR("semop P error"); return 0; } int V(int semid, int which) { struct sembuf op = { .sem_num = which, .sem_op = 1, .sem_flg = 0, }; if (semop(semid, &op, 1)) PRINT_ERR("semop V error"); return 0; } int main(int argc, const char* argv[]) { key_t key; int shmid, semid; char* raddr; if ((key = ftok("/home", 'g')) == -1) PRINT_ERR("ftok error"); if ((shmid = shmget(key, SHMSIZE, IPC_CREAT | 0666)) == -1) PRINT_ERR("shmget error"); if ((raddr = shmat(shmid, NULL, 0)) == (void*)-1) PRINT_ERR("shmat error");
if ((semid = semget(key, 2, IPC_CREAT | IPC_EXCL | 0666)) == -1) { if (errno = EEXIST) { semid = semget(key, 2, IPC_CREAT | 0666); } else { PRINT_ERR("semget error"); } } else { mysem_init(semid, 0, 1); mysem_init(semid, 1, 0); }
while (1) { P(semid, 1); printf("read:%s\n", raddr); V(semid, 0); if (strncmp(raddr, "quit", 4) == 0) break; } if (shmdt(raddr)) PRINT_ERR("shmdt error"); if (shmctl(shmid, IPC_RMID, NULL)) PRINT_ERR("shmctl error"); if (semctl(semid, 0, IPC_RMID)) PRINT_ERR("semctl error"); return 0; }
|
套接字
socket
套接字本来是用于同主机的进程间通信的。后来有了TCP/IP协议族加入后又进行的功能的拓展与升级,实现了多个不同主机间的进程间通信。
socket
是一个系统调用的接口,会返回一个文件描述符,用户通过网络收发数据时,只需对此文件描述符进行读写操作即可,读就是接收,写就是发送。相当于把复杂的网络通信过程转换成了IO操作。
此处之涉及本地间的通信,不涉及网络通信,更多在另一篇文章中进行介绍。
本地间 TCP
通信
服务器
- 创建套接字
socket
- 填充服务器本地信息结构体
sockaddr_un
- 将套接字与服务器本地信息结构体绑定
bind
- 将套接字设置为被动监听状态
listen
- 阻塞等待客户端的连接请求
accept
- 进行通信
recv
/ send
或 read
/ write
客户端
- 创建套接字
socket
- 填充服务器本地信息结构体
sockaddr_un
- 发送客户端的连接请求
connect
- 进行通信
send
/ recv
本地通信结构体
1 2 3 4
| struct sockaddr_un { sa_family_t sun_family; char sun_path[108]; };
|
客户端
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
| #include <stdio.h> #include <arpa/inet.h> #include <sys/socket.h> #include <sys/types.h> #include <netinet/in.h> #include <netinet/ip.h> #include <string.h> #include <unistd.h> #include <stdbool.h> #include <sys/un.h>
int main(int argc, char const *argv[]) { int socket_fd = socket(AF_LOCAL,SOCK_STREAM,0); if(socket_fd == -1) { perror("socket err:"); return -1; } struct sockaddr_un serverInfo = {0}; serverInfo.sun_family = AF_LOCAL; const char* processfileName = "myserver"; memcpy(serverInfo.sun_path,processfileName,strlen(processfileName));
int ret = connect(socket_fd,(const struct sockaddr*)&serverInfo,sizeof(serverInfo)); if(ret == -1) { perror("connect err:"); return -1; } char buf[128] = {0}; while(true) { memset(buf,0,sizeof(buf)); fgets(buf,sizeof(buf),stdin); int nbytes = write(socket_fd,buf,strlen(buf)); if(nbytes == -1) { perror("write err:"); return -1; } memset(buf,0,sizeof(buf)); nbytes = read(socket_fd,buf,sizeof(buf)); if(nbytes == -1) { perror("read err:"); return -1; } if(nbytes == 0) { printf("对端已经关闭了\n"); return 0; } printf("服务器发来的数据: %s \n",buf); } 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 64 65 66 67 68 69 70 71 72 73 74 75 76
| #include <stdio.h> #include <arpa/inet.h> #include <sys/socket.h> #include <sys/types.h> #include <netinet/in.h> #include <netinet/ip.h> #include <string.h> #include <unistd.h> #include <stdbool.h> #include <sys/un.h>
int main(int argc, char const *argv[]) { int listen_fd = socket(AF_LOCAL, SOCK_STREAM, 0); if (listen_fd == -1) { perror("socket() err:"); return -1; } struct sockaddr_un serverInfo; memset(&serverInfo, 0, sizeof(serverInfo)); serverInfo.sun_family = AF_LOCAL; const char* processfileName = "myserver"; memcpy(serverInfo.sun_path,processfileName,strlen(processfileName)); int ret = bind(listen_fd, (const struct sockaddr *)&serverInfo, sizeof(serverInfo)); if (ret == -1) { perror("bind err:"); return -1; } ret = listen(listen_fd, 1); if (ret == -1) { perror("listen err:"); return -1; } printf("本地套接字通信服务进程启动\n"); while (true) { int connect_fd = accept(listen_fd, NULL, NULL); if (connect_fd == -1) { perror("accept err:"); return -1; } while (true) { char buf[128] = {0}; int nbtyes = read(connect_fd, buf,sizeof(buf)); if (nbtyes == -1) { perror("read err:"); return -1; } if(nbtyes == 0) { printf("对端断开了连接\n"); close(connect_fd); break; } printf("客户端发来的数据为 %s \n",buf); } }
return 0; }
|
本地间 UDP
通信
服务器:
- 创建套接字
socket
- 填充服务器本地信息结构体
sockaddr_un
- 将套接字与服务器本地信息结构体绑定
bind
- 进行通信
recvfrom
/ sendto
客户端:
- 创建套接字
socket
- 填充客户端本地信息结构体
sockaddr_un
- 将套接字与客户端本地信息结构体绑定
bind
,如果不绑定 服务器没法给客户端回信
- 填充服务器本地信息结构体 sockaddr_un
- 进行通信 sendto/ recvfrom
服务器
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
| #include <stdio.h> #include <sys/socket.h> #include <sys/types.h> #include <stdbool.h> #include <unistd.h> #include <arpa/inet.h> #include <netinet/in.h> #include <netinet/ip.h> #include <string.h> #include <stdlib.h> #include <sys/un.h> int main(int argc, char const *argv[]) { int socket_dgram = socket(AF_LOCAL,SOCK_DGRAM,0); if(socket_dgram == -1) { perror("socket err:"); return -1; }
struct sockaddr_un serverInfo = {0}; serverInfo.sun_family = AF_LOCAL; const char* processfileName = "Local_udp_server"; memcpy(serverInfo.sun_path,processfileName,strlen(processfileName));
int ret = bind(socket_dgram,(struct sockaddr*)&serverInfo,sizeof(serverInfo)); if(ret == -1) { perror("bind err:"); return -1; } struct sockaddr_un udp_clientInfo = {0}; int info_len = sizeof(udp_clientInfo); char buf[128] = {0}; printf("本地udp进程服务启动\n"); while(true) { memset(buf,0,sizeof(buf)); int nbytes = recvfrom(socket_dgram,buf,sizeof(buf),0, (struct sockaddr*)&udp_clientInfo,&info_len); if(nbytes == -1) { perror("recvfrom err:"); return -1; } if(nbytes == 0) { printf("没有接收到任何数据\n"); continue; } printf("udp客户进程文件:[%s] 发来数据->\n", udp_clientInfo.sun_path); printf("udp服务器接收的数据为: %s \n",buf); nbytes = sendto(socket_dgram,buf,strlen(buf),0, (struct sockaddr*)&udp_clientInfo,sizeof(udp_clientInfo)); if(nbytes == -1) { perror("sendto err:"); return -1; } } 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 64 65 66 67
| #include <stdio.h> #include <sys/socket.h> #include <sys/types.h> #include <arpa/inet.h> #include <netinet/ip.h> #include <netinet/in.h> #include <stdlib.h> #include <string.h> #include <stdbool.h> #include <sys/un.h>
int main(int argc, char const *argv[]) { int udp_fd = socket(AF_LOCAL, SOCK_DGRAM, 0); if (udp_fd == -1) { perror("socket err:"); return -1; }
struct sockaddr_un udp_serverInfo = {0}; udp_serverInfo.sun_family = AF_LOCAL; const char* processfileName = "Local_udp_server"; memcpy(udp_serverInfo.sun_path, processfileName,strlen(processfileName)); struct sockaddr_un udp_clientInfo = {0}; udp_clientInfo.sun_family = AF_LOCAL; const char* clientfileName = "Local_udp_client"; memcpy(udp_clientInfo.sun_path, clientfileName,strlen(clientfileName)); socklen_t len = sizeof(udp_clientInfo); int ret = bind(udp_fd, (struct sockaddr*)&udp_clientInfo, len);
char buf[128] = {0}; while (true) { memset(buf, 0, sizeof(buf)); printf("请输入你要发送的udp数据:\n"); fgets(buf, sizeof(buf), stdin); int nbytes = sendto(udp_fd, buf, strlen(buf), 0, (struct sockaddr*)&udp_serverInfo, sizeof(udp_serverInfo)); if (nbytes == -1) { perror("sendto err:"); return -1; } memset(buf, 0, sizeof(buf)); nbytes = recvfrom(udp_fd, buf, sizeof(buf) - 1, 0, (struct sockaddr*)&udp_clientInfo, &len); if (nbytes == -1) { perror("recvfrom err:"); return -1; } printf("对端发来的数据为:%s \n", buf); } return 0; }
|
守护进程
守护进程是后台运行的进程,它会随着系统的启动而启动,会随着系统的终止而终止,类似于 windows 上的各种服务。
例如 windows 上的网络管理的服务,ubuntu 上的 sshd
的服务,ubuntu 上的 资源管理 的进程
守护进程创建的流程
创建孤儿进程
设置孤儿进程的会话id和组id
切换目录到/var/log目录下
1 2 3 4 5 6
| int chdir(const char *path);
|
设置umask的值0
1 2 3 4 5 6 7 8
| #include <sys/types.h> #include <sys/stat.h>
mode_t umask(mode_t mask);
|
创建日志文件
1 2 3
| int fd; if((fd = open("./daemon.log",O_RDWR|O_CREAT|O_APPEND,0666))==-1) PRINT_ERR("open error");
|
文件描述符重定向工作
1 2 3
| dup2(fd,0); dup2(fd,1); dup2(fd,2);
|
开启自己的服务
文件描述符重定向
dup
dup2
函数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| #include <unistd.h>
int dup(int oldfd);
int dup2(int oldfd, int newfd);
|
dup
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
| #include <head.h>
int main(int argc, const char* argv[]) { int fd; if ((fd = open("./daemon.log", O_RDWR | O_CREAT | O_APPEND, 0666)) == -1) PRINT_ERR("open error"); close(0); close(1); close(2);
dup(fd); dup(fd); dup(fd);
printf("hello daemon dup process...\n"); fflush(stdout); fprintf(stderr,"this is test daemon dup stderr...\n"); lseek(fd,0,SEEK_SET); char ch; scanf("%c",&ch); printf("ch = %c\n",ch); return 0; }
|
dup2
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| #include <head.h>
int main(int argc, const char* argv[]) { int fd; if ((fd = open("./daemon.log", O_RDWR | O_CREAT | O_APPEND, 0666)) == -1) PRINT_ERR("open error"); dup2(fd,0); dup2(fd,1); dup2(fd,2); printf("hello daemon dup process...\n"); fflush(stdout); fprintf(stderr,"this is test daemon dup stderr...\n"); lseek(fd,0,SEEK_SET); char ch; scanf("%c",&ch); printf("ch = %c\n",ch); 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
| #include <head.h>
int main(int argc, const char* argv[]) { pid_t pid; int fd; if ((pid = fork()) == -1) { PRINT_ERR("fork error"); } else if (pid == 0) { if(setsid()==-1) PRINT_ERR("setsid error"); if (chdir("/")) PRINT_ERR("chdir error"); umask(0); if ((fd = open("./daemon.log", O_RDWR | O_CREAT | O_APPEND, 0666)) == -1) PRINT_ERR("open error"); dup2(fd,0); dup2(fd,1); dup2(fd,2); char s[] = "i am test daemon process\n"; while(1){ write(1,s,strlen(s)); sleep(1); }
} else { exit(0); } return 0; }
|
进程内程序替换函数
终端执行 a.out
程序, 首先会执行fork产生一个子进程,当产生子进程之后,子进程的 .text
段存放的是终端的可执行程序,需要将这个段内的内容替换成 a.out
。此时就可以执行 a.out
应用程序了,以上的功能可以通过 system
完成。
system
函数
1 2 3 4 5 6 7 8 9 10 11
| #include <stdlib.h>
int system(const char *command);
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| #include <head.h>
int main(int argc, const char* argv[]) {
if (system("./myshell.sh 111 222 333 4444 555")) PRINT_ERR("system error");
return 0; }
|
函数接口
fopen
fclose
fgetc
fputc
fgets
fputs
fread
fwrite
fseek
ftell
rewind
perror
strerror
snprintf
sprintf
fprintf
time
localtime
open
read
write
close
lseek
stat
lstat
getpwuid
getgrgid
opendir
readdir
closedir
fork
getpid
getppid
exit
_exit
wait
waitpid
dup
dup2
system
pthread_create
pthread_self
pthread_exit
pthread_join
pthread_detach
pthread_cancel
pthread_mutex_init
pthread_mutex_lock
pthread_mutex_unlock
pthread_mutex_destroy
sem_init
sem_wait
sem_post
sem_destroy
pthread_cond_init
pthread_cond_wait
pthread_cond_signal
pthread_cond_broatcast
pthread_cond_destroy
pipe
mkfifo
signal
kill
raise
alarm
ftok
msgget
msgsnd
msgrcv
msgctl
shmget
shmat
shmdt
shmctl
semget
semctl
semop