#include
#include
#include
int main() {
int num = 10;
// 创建子进程
pid_t pid = fork();
// 判断是父进程还是子进程
if(pid > 0) {
// printf("pid : %d\n", pid);
// 如果大于0,返回的是创建的子进程的进程号,当前是父进程
printf("i am parent process, pid : %d, ppid : %d\n", getpid(), getppid());
printf("parent num : %d\n", num);
num += 10; //父进程+10 不影响子进程
printf("parent num += 10 : %d\n", num);
} else if(pid == 0) {
// 当前是子进程
printf("i am child process, pid : %d, ppid : %d\n", getpid(),getppid());
printf("child num : %d\n", num);
num += 100;
printf("child num += 100 : %d\n", num);
}
// for循环
for(int i = 0; i < 3; i++) {
printf("i : %d , pid : %d\n", i , getpid());
sleep(1);
}
return 0;
}
在某一状态下,如子进程刚被创建出来
set follow-fork-mode (child / parent )
更改。set detach-on-fork
有bug。需要8.0一下的版本,可以下Ubuntu 16 解决。#include
int execl(const char *path, const char *arg, ...);
- 参数:
- path:需要指定的执行的文件的路径或者名称
a.out /home/nowcoder/a.out 推荐使用绝对路径
./a.out hello world
- arg:是执行可执行文件所需要的参数列表
第一个参数一般没有什么作用,为了方便,一般写的是执行的程序的名称
从第二个参数开始往后,就是程序执行所需要的的参数列表。
参数最后需要以NULL结束(哨兵)
- 返回值:
只有当调用失败,才会有返回值,返回-1,并且设置errno
如果调用成功,没有返回值。
#include
int execlp(const char *file, const char *arg, ... );
会到环境变量中查找指定的可执行文件,如果找到了就执行,找不到就执行不成功。
参数:
file:需要执行的可执行文件的文件名
如: a.out 、 ps
arg:是执行可执行文件所需要的参数列表
第一个参数一般没有什么作用,为了方便,一般写的是执行的程序的名称
从第二个参数开始往后,就是程序执行所需要的的参数列表。
参数最后需要以NULL结束(哨兵)
返回值:
只有当调用失败,才会有返回值,返回-1,并且设置errno
如果调用成功,没有返回值。
#include
int execv(const char *path, char *const argv[]);
char * argv[] = {"ps", "aux", NULL};
execv("/bin/ps", argv);
int execve(const char *filename, char *const argv[], char *const envp[]);
char * envp[] = {"/home/nowcoder", "/home/bbb", "/home/aaa"};
envp: 是当前可执行文件时会在当前提供的环境变量中("/home/nowcoder"等)查找。
#include
#include //exit(int val)
#include //_exit(int val)
int main() {
printf("hello\n"); // '\n' 有刷新缓冲区的作用,使得hello 输出
printf("world"); // 没有‘\n’ 没有刷新缓冲区的作用 使得word 还存在于缓冲区
// exit(0); //存在刷新缓冲区的步骤,word 有输出。
_exit(0); //没有刷新缓冲区的步骤,word还在缓冲区中没有输出
return 0;
}
#include
#include
pid_t wait(int *wstatus);
功能:等待任意一个子进程结束,如果任意一个子进程结束了,次函数会回收子进程的资源。
参数:int *wstatus
进程退出时的状态信息,传入的是一个int类型的地址,传出参数。
返回值:
- 成功:返回被回收的子进程的id
- 失败:-1 (所有的子进程都结束,调用函数失败)
调用wait函数的进程会被挂起(阻塞),直到它的一个子进程退出或者收到一个不能被忽略的信号时才被唤醒(相当于继续往下执行)
如果没有子进程了,函数立刻返回,返回-1;如果子进程都已经结束了,也会立即返回,返回-1.
int main() {
// 有一个父进程,创建5个子进程(兄弟)
pid_t pid;
// 创建5个子进程
for(int i = 0; i < 5; i++) {
pid = fork();
if(pid == 0) {
break;
}
}
if(pid > 0) {
// 父进程
while(1) {
printf("parent, pid = %d\n", getpid());
// int ret = wait(NULL);
int st;
int ret = wait(&st);
if(ret == -1) {
break;
}
if(WIFEXITED(st)) {
// 是不是正常退出
printf("退出的状态码:%d\n", WEXITSTATUS(st));
}
if(WIFSIGNALED(st)) {
// 是不是异常终止
printf("被哪个信号干掉了:%d\n", WTERMSIG(st));
}
printf("child die, pid = %d\n", ret);
sleep(1);
}
} else if (pid == 0){
// 子进程
while(1) {
printf("child, pid = %d\n",getpid());
sleep(1);
}
exit(0); //0为状态码,通常用零表示正常结束
}
return 0; // exit(0)
}
#include
#include
pid_t waitpid(pid_t pid, int *wstatus, int options);
功能:回收指定进程号的子进程,可以设置是否阻塞。
参数:
管道(匿名,有名)、 信号(量)、消息队列、共享内存、套接字
注意:读取后目标数据后,管道中的对应内存被释放,供后续写入数据使用。
#include
int pipe(int pipefd[2]);
#include
#include
#include
#include
int main(){
int pipefd[2];
int ret = pipe(pipefd);
if(ret == -1) { //失败打出错误信息
perror("pipe");
exit(0);
}
pid_t pid = fork();
if(pid > 0) {
//父进程 读
char buf[1024] = {0};
int len = read(pipefd[0],buf,sizeof(buf)); //若管道中没有数据默认阻塞 read 是阻塞的!
printf("parent recv : %s,pid : %d\n",buf,getpid());
} else if(pid == 0) {
//子进程 写
// sleep(10); //测试read 阻塞
const char * str = "I am child process";
write(pipefd[1],str,strlen(str));
}
return 0;
}
控制端输入ulimit -a
或者 用fpathconf
函数 参数用 宏_PC_PIPE_BUF
#include
#include
#include
#include
#include
int main() {
int pipefd[2];
int ret = pipe(pipefd);
// 获取管道的大小
long size = fpathconf(pipefd[0], _PC_PIPE_BUF);
printf("pipe size : %ld\n", size);
return 0;
}
应在父 / 子进程中关闭写 close(pipefd[1]);
/ 读 close(pipefd[0]);
端
简单来说就是一个进程读一个进程写。
重点内容注意
//匿名管道通信案例
#include //exit()
#include //pipe
#include
#include
#include //fork()
#include
int main() {
//创建一个管道
int pipefd[2];
int ret = pipe(pipefd);
if(ret == -1){
perror("pipe");
return 0;
}
//创建子进程
pid_t pid = fork();
if(pid > 0) {
//父进程
//关闭写端
close(pipefd[1]);
//读
char buf[1024];
int len = -1;
while ((len = read(pipefd[0],buf,sizeof(buf) - 1))>0)
{
//过滤
printf("%s",buf);
bzero(buf,1024); //buf 清空 read 变为阻塞状态
}
wait(NULL); //回收子进程
} else if(pid == 0) {
//子进程
//关闭读端
close(pipefd[0]);
//重定向
dup2(pipefd[1],STDOUT_FILENO); // STDIN_FILENO:接收键盘的输入
//STDOUT_FILENO:向屏幕输出 ,属于文件描述符
//执行 ps aux
while (1)
{
execlp("ps","ps","aux",NULL); //若管道满了 则当前写操作阻塞
perror("execl");
}
exit(0);
} else {
perror("fork");
exit(0);
}
return 0;
}
管道特点总结:
读管道:
管道中有数据,read返回实际读到的字节数。
管道中无数据:
写端被全部关闭,read返回0(相当于读到文件的末尾)
写端没有完全关闭,read阻塞等待
写管道:
管道读端全部被关闭,进程异常终止(进程收到SIGPIPE信号)
管道读端没有全部关闭:
管道已满,write阻塞
管道没有满,write将数据写入,并返回实际写入的字节数
设置管道为非阻塞
重点提醒
O_NONBLOCK
表示非阻塞 #include
#include
#include
#include
#include
#include
/*
设置管道非阻塞
int flags = fcntl(fd[0], F_GETFL); // 获取原来的flag
flags |= O_NONBLOCK; // 修改flag的值
fcntl(fd[0], F_SETFL, flags); // 设置新的flag
*/
int main() {
// 在fork之前创建管道
int pipefd[2];
int ret = pipe(pipefd);
if(ret == -1) {
perror("pipe");
exit(0);
}
// 创建子进程
pid_t pid = fork();
if(pid > 0) {
// 父进程
printf("i am parent process, pid : %d\n", getpid());
// 关闭写端
close(pipefd[1]);
// 从管道的读取端读取数据
char buf[1024] = {0};
int flags = fcntl(pipefd[0], F_GETFL); // 获取原来的flag
flags |= O_NONBLOCK; // 修改flag的值 //非阻塞
fcntl(pipefd[0], F_SETFL, flags); // 设置新的flag
while(1) {
int len = read(pipefd[0], buf, sizeof(buf)); //输出-1 表示没有数据
printf("len : %d\n", len);
printf("parent recv : %s, pid : %d\n", buf, getpid());
memset(buf, 0, 1024);
sleep(1);
}
} else if(pid == 0){
// 子进程
printf("i am child process, pid : %d\n", getpid());
// 关闭读端
close(pipefd[0]);
char buf[1024] = {0};
while(1) {
// 向管道中写入数据
char * str = "hello,i am child";
write(pipefd[1], str, strlen(str));
sleep(5);
}
}
return 0;
}
可以发现设置管道读端为非阻塞,当没有管道没有数据时,read不阻塞 继续运行 返回-1。buf中没有数据,输出为空。
#include
#include
int mkfifo(const char *pathname, mode_t mode);
有名管道的注意事项:
1. 一个为只读而打开一个管道的进程会阻塞,直到另外一个进程为只写打开管道
2. 一个为只写而打开一个管道的进程会阻塞,直到另外一个进程为只读打开管道
读管道:
管道中有数据,read返回实际读到的字节数
管道中无数据:
管道写端被全部关闭,read返回0,(相当于读到文件末尾)
写端没有全部被关闭,read阻塞等待
写管道:
管道读端被全部关闭,进行异常终止(收到一个SIGPIPE信号)
管道读端没有全部关闭:
管道已经满了,write会阻塞
管道没有满,write将数据写入,并返回实际写入的字节数。
#include
#include
#include
#include
#include
#include
#include
int main() {
//检查有名管道是否存在,不存在则创建 mkfifo()
int ret = access("fifo1",F_OK);
if(ret == -1) {
printf("有名管道fifo1不存在,正在创建。。\n");
ret = mkfifo("fifo1",0664);
if(ret == -1) {
perror("fifo1");
exit(0);
}
}
ret = access("fifo2",F_OK);
if(ret == -1) {
printf("有名管道fifo2不存在,正在创建。。\n");
ret = mkfifo("fifo2",0664);
if(ret == -1) {
perror("fifo2");
exit(0);
}
}
//以只读的方式打开管道fifo1
int fdr = open("fifo1",O_RDONLY);
if(fdr == -1) {
perror("fifo1");
exit(0);
}
printf("fifo1管道打开成功!\n");
//以只写的方式打开管道fifo2
int fdw = open("fifo2",O_WRONLY);
if(fdw == -1) {
perror("fifo2");
exit(0);
}
printf("fifo2管道打开成功!\n");
//循环写读数据
char buf[128];
while(1) {
//读管道
memset(buf,0,128);
read(fdr,buf,sizeof(buf));//管道有数据 返回读到的字节数。管道无数据,当写端全部关闭 返回0,当写端还没关闭则阻塞
if(read <= 0) {
perror("read");
exit(0);
}
printf("recv chater: %s\n",buf);
//写数据
memset(buf,0,128);
fgets(buf,128,stdin);
ret =write(fdw,buf,sizeof(buf));
if(ret == -1) {
perror("write");
exit(0);
}
}
//关闭文件描述符
close(fdr);
close(fdw);
return 0;
}
后续改进建议:创建一个子进程去读,父进程去写,可以并发的执行读写。防止因为读阻塞了后续无法写入或者写阻塞后续无法读这两个情况。
#include
void *mmap(void *addr, size_t length, int prot, int flags,int fd, off_t offset);
功能:将一个文件或者设备的数据映射到内存中。
参数:
返回值:返回创建的内存的首地址
MAP_FAILED
,实质上是 指向void类型的指针 (void *) -1
#include
int munmap(void *addr, size_t length);
功能:释放内存映射
参数:
实验源码在linux下的lesson21
没有关系的进程间通信
- 准备一个大小不是0的磁盘文件
- 进程1 通过磁盘文件创建内存映射区
- 得到一个操作这块内存的指针
- 进程2 通过磁盘文件创建内存映射区
- 得到一个操作这块内存的指针
- 使用内存映射区通信
注意:内存映射区通信,是非阻塞。
.c
打开同一个文件并申请内存映射区,一个.c
读,另一个.c
写。最后关闭文件,关闭内存映射区//创建为已经存在的文件,创建内存映射区
//新创建一个文件,然后为其拓展(使内容不为空),也创建一个内存映射区
//利用memcpy 进行内存拷贝。
#include
#include
#include
#include
#include
#include
#include
#include
int main() {
//打开要复制的文件 可读可写,不能没有读权限,否则mmap调用会出错
int fd = open("english.txt",O_RDWR);
if(fd == -1) {
perror("open1");
exit(0);
}
//获取文件大小
int size = lseek(fd,0,SEEK_END);
//打开新的文件
int fd1 = open("copy.txt",O_RDWR | O_CREAT,0664);
if (fd1 == -1)
{
perror("open2");
exit(0);
}
//为新文件拓展
truncate("copy.txt",size);
write(fd1," ",1);
//创建内存映射区
char * ptr = mmap(NULL,size,PROT_READ|PROT_WRITE,MAP_SHARED,fd,0);
char * ptr1 = mmap(NULL,size,PROT_READ|PROT_WRITE,MAP_SHARED,fd1,0);
//内存拷贝
memcpy(ptr1,ptr,size);
//关闭虚拟内存,文件
munmap(ptr,size);
munmap(ptr1,size);
close(fd);
close(fd1);
return 0;
}
匿名映射:不需要文件实体进程一个内存映射
将MAP_ANONYMOUS传入 mmap函数 可以实现
#include
#include
#include
#include
#include
#include
#include
#include
#include
int main() {
// 1.创建匿名内存映射区
int len = 4096;
void * ptr = mmap(NULL, len, PROT_READ | PROT_WRITE, MAP_SHARED | MAP_ANONYMOUS, -1, 0);
if(ptr == MAP_FAILED) {
perror("mmap");
exit(0);
}
// 父子进程间通信
pid_t pid = fork();
if(pid > 0) {
// 父进程
strcpy((char *) ptr, "hello, world");
wait(NULL);
}else if(pid == 0) {
// 子进程
sleep(1);
printf("%s\n", (char *)ptr);
}
// 释放内存映射区
int ret = munmap(ptr, len);
if(ret == -1) {
perror("munmap");
exit(0);
}
return 0;
}
munmap(ptr, len); // 错误,要保存地址
2.如果open时O_RDONLY, mmap时prot参数指定PROT_READ | PROT_WRITE会怎样?
错误,返回MAP_FAILED
open()函数中的权限建议和prot参数的权限保持一致。
3.如果文件偏移量为1000会怎样?
偏移量必须是4K的整数倍,返回MAP_FAILED
4.mmap什么情况下会调用失败?
第二个参数:length = 0
第三个参数:prot
第5个参数fd 通过open函数时指定的其中一个 O_RDONLY / O_WRONLY,mmap传入 prot PROT_READ | PROT_WRITE
会出错
总而言之,无论谁,一定要有读权限。
5.可以open的时候O_CREAT一个新文件来创建映射区吗?
可以的,但是创建的文件的大小如果为0的话,肯定不行
可以对新的文件进行扩展
lseek()
truncate()
6.mmap后关闭文件描述符,对mmap映射有没有影响?
int fd = open(“XXX”);
mmap(,fd,0);
close(fd);
映射区还存在,创建映射区的fd被关闭,没有任何影响。
7.对ptr越界操作会怎样?
void * ptr = mmap(NULL, 100,);
不会只开辟100,开辟4K的整数倍。
越界操作操作的是非法的内存 -> 段错误
注意SIGKILL
不能杀死僵尸进程(子进程中PCB资源没有被回收)。
#include
#include
int main() {
char* buf;
strcpy(buf,"hello world");
return 0;
}
运行后会出错,产生命名为core的文件(需要用ulimit -a 去设置core file部分不为0),文件中保存错误信息。
#include
#include
int kill(pid_t pid, int sig);
kill(getppid(), 9);//向父进程发送
kill(getpid(), 9);//向子进程发送
#include
#include
int raise(int sig);
raise
相同功能kill(getpid(), sig);
abort
相同功能kill(getpid(), SIGABRT);
#include
unsigned int alarm(unsigned int seconds);
功能:设置定时器(闹钟)。函数调用,开始倒计时,当倒计时为0的时候,
函数会给当前的进程发送一个信号:SIGALARM
参数:
seconds: 倒计时的时长,单位:秒。如果参数为0,定时器无效(不进行倒计时,不发信号)。
取消一个定时器,通过alarm(0)。
返回值:
之前没有定时器,返回0
之前有定时器,返回之前的定时器剩余的时间
SIGALARM :默认终止当前的进程,每一个进程都有且只有唯一的一个定时器。
alarm(10); -> 返回0
过了1秒
alarm(5); -> 返回9
alarm(100) -> 该函数是不阻塞的
实际完成输出的时间大于1s
实际的时间 = 内核时间 + 用户时间 + 消耗的时间
进行文件IO操作的时候比较浪费时间
定时器,与进程的状态无关(自然定时法)。无论进程处于什么状态,alarm都会计时。
#include
int setitimer(int which, const struct itimerval *new_value,
struct itimerval *old_value);
功能:设置定时器(闹钟)。可以替代alarm函数。精度微妙us,可以实现周期性定时
参数:
which : 定时器以什么时间计时
ITIMER_REAL: 真实时间,时间到达,发送 SIGALRM 常用
ITIMER_VIRTUAL: 用户时间,时间到达,发送 SIGVTALRM
ITIMER_PROF: 以该进程在用户态和内核态下所消耗的时间来计算,时间到达,发送 SIGPROF
new_value: 设置定时器的属性
struct itimerval { // 定时器的结构体
struct timeval it_interval; // 每个阶段的时间,间隔时间
struct timeval it_value; // 延迟多长时间执行定时器
};
struct timeval { // 时间的结构体
time_t tv_sec; // 秒数
suseconds_t tv_usec; // 微秒
};
第一次开始过10秒后,每个2秒定时一次,就是设置it_interval.tv_sec =2
和it_value.tv_sec = 10;
,其中必须初始化 suseconds_t tv_usec
微秒,否则是随机值,运行会出错。可以设置为0
old_value :记录上一次的定时的时间参数,一般不使用,指定NULL
返回值:
成功 0
失败 -1 并设置错误号
由于定时器到时间了,会杀死进程,故没有间隔定时的体现,下文将讲到信号捕捉函数,可以捕捉信号。
#include
typedef void (*sighandler_t)(int);
sighandler_t signal(int signum, sighandler_t handler);
void (*sighandler_t)(int);
传入 int类型变量可以接受捕捉到信号的参数值回调函数:
需要程序员实现,提前准备好的,函数的类型根据实际需求,看函数指针的定义
不是程序员调用,而是当信号产生,由内核调用
函数指针是实现回调的手段,函数实现之后,将函数名放到函数指针的位置就可以了。
返回值:
成功,返回上一次注册的信号处理函数的地址。第一次调用返回NULL
失败,返回SIG_ERR,设置错误号
SIGKILL
、SIGSTOP
不能被捕捉,不能被忽略。
该函数必须放到创建定时器代码的前面
void myalarm(int num) {
printf("捕捉到了信号的编号是:%d\n", num);
printf("xxxxxxx\n");
}
// 过3秒以后,每隔2秒钟定时一次
int main() {
// 注册信号捕捉
// signal(SIGALRM, SIG_IGN);
// signal(SIGALRM, SIG_DFL);
// void (*sighandler_t)(int); 函数指针,int类型的参数表示捕捉到的信号的值。
signal(SIGALRM, myalarm);
struct itimerval new_value;
// 设置间隔的时间
new_value.it_interval.tv_sec = 2;
new_value.it_interval.tv_usec = 0;
// 设置延迟的时间,3秒之后开始第一次定时
new_value.it_value.tv_sec = 3;
new_value.it_value.tv_usec = 0;
int ret = setitimer(ITIMER_REAL, &new_value, NULL); // 非阻塞的
printf("定时器开始了...\n");
if(ret == -1) {
perror("setitimer");
exit(0);
}
getchar();
return 0;
}
1.用户通过键盘 Ctrl + C, 产生2号信号SIGINT (信号被创建)
2.信号产生但是没有被处理 (未决)
- 在内核中将所有的没有被处理的信号存储在一个集合中 (未决信号集)
- SIGINT信号状态被存储在第二个标志位上
- 这个标志位的值为0, 说明信号不是未决状态
- 这个标志位的值为1, 说明信号处于未决状态
3.这个未决状态的信号,需要被处理,处理之前需要和另一个信号集(阻塞信号集),进行比较
- 阻塞信号集默认不阻塞任何的信号
- 如果想要阻塞某些信号需要用户调用系统的API
4.在处理的时候和阻塞信号集中的标志位进行查询,看是不是对该信号设置阻塞了
- 如果没有阻塞,这个信号就被处理
- 如果阻塞了,这个信号就继续处于未决状态,直到阻塞解除,这个信号就被处理
以下信号集相关的函数都是对自定义的信号集进行操作。
int sigemptyset(sigset_t *set);
int sigfillset(sigset_t *set);
int sigaddset(sigset_t *set, int signum);
int sigdelset(sigset_t *set, int signum);
int sigismember(const sigset_t *set, int signum);
int sigprocmask(int how, const sigset_t *set, sigset_t *oldset);
int sigpending(sigset_t *set);
//编写一个程序,把所有的常规信号(1-31)的未决状态打印到屏幕
// 设置某些信号是阻塞的,通过键盘产生这些信号
//创建自己的信号集 提供给内核设置信号阻塞或非阻塞。 某信号设置为阻塞状态后,用户端发出信号,可以在系统内核的未决信号集中发现对应信号位置 置1
#include
#include
#include
int main () {
int num = 0;
while(1) {
num++;
sigset_t set;
//清空信号集
sigemptyset(&set);
//添加到信号集
sigaddset(&set,SIGINT);
sigaddset(&set,SIGQUIT);
//将自定义信号集中的数据设置到内核中(设置阻塞,解除阻塞,替换)
sigprocmask(SIG_BLOCK,&set,NULL);
//创建一个信号集,接收系统的未决信号集(即还没递达的信号)
sigset_t sigpend;
sigemptyset(&sigpend);
sigpending(&sigpend);
for(int i = 1; i <= 32;i++) { //检测信号集中是否有阻塞的信号 ,有则返回1
if(sigismember(&sigpend,i) ==1 ) {
printf("1");
} else if(sigismember(&sigpend,i)== 0) {
printf("0");
}
}
printf("\n");
sleep(1);
if(num == 10) {
// 解除阻塞
sigprocmask(SIG_UNBLOCK, &set, NULL);
}
}
return 0;
}
#include
int sigaction(int signum, const struct sigaction *act,
struct sigaction *oldact);
功能:检查或者改变信号的处理。信号捕捉
参数:
signum : 需要捕捉的信号的编号或者宏值(信号的名称)
act :捕捉到信号之后的处理动作
oldact : 上一次对信号捕捉相关的设置,一般不使用,传递NULL
返回值:
成功 0
失败 -1
参数中的结构体介绍
1.注意只有在信号捕捉函数才会使用临时阻塞信号集 sigset_t sa_mask
·,捕捉函数执行完后临时阻塞信号集不再启用,会还原为内核的阻塞信号集。
struct sigaction {
// 函数指针,指向的函数就是信号捕捉到之后的处理函数 , int数据类型用于接收信号的对应序号
void (*sa_handler)(int);
// 不常用
void (*sa_sigaction)(int, siginfo_t *, void *);
// 临时阻塞信号集,在信号捕捉函数执行过程中,临时阻塞某些信号。
sigset_t sa_mask;
// 使用哪一个信号处理对捕捉到的信号进行处理
// 这个值可以是0,表示使用sa_handler,也可以是SA_SIGINFO表示使用sa_sigaction
int sa_flags;
// 被废弃掉了
void (*sa_restorer)(void);
};
act.sa_handler = myalarm;
#include
#include
#include
#include
void myalarm(int num) {
printf("捕捉到了信号的编号是:%d\n", num);
printf("xxxxxxx\n");
}
// 过3秒以后,每隔2秒钟定时一次
int main() {
struct sigaction act;
act.sa_flags = 0;
act.sa_handler = myalarm;
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;
// 设置延迟的时间,3秒之后开始第一次定时
new_value.it_value.tv_sec = 3;
new_value.it_value.tv_usec = 0;
int ret = setitimer(ITIMER_REAL, &new_value, NULL); // 非阻塞的
printf("定时器开始了...\n");
if(ret == -1) {
perror("setitimer");
exit(0);
}
// getchar();
while(1);
return 0;
}
/*
SIGCHLD信号产生的3个条件:
1.子进程结束
2.子进程暂停了
3.子进程继续运行
都会给父进程发送该信号,父进程默认忽略该信号。
使用SIGCHLD信号解决僵尸进程的问题。
注意提前设置好自定义阻塞信号集的目的提前设置好阻塞信号集,阻塞SIGCHLD,因为有可能子进程很快结束,父进程还没有注册完信号捕捉
#include
#include
#include
#include
#include
#include
void myFun(int num) {
printf("捕捉到的信号 :%d\n", num);
// 回收子进程PCB的资源
// while(1) {
// wait(NULL);
// }
while(1) {
int ret = waitpid(-1, NULL, WNOHANG);
if(ret > 0) {
printf("child die , pid = %d\n", ret);
} else if(ret == 0) {
// 说明还有子进程或者
break;
} else if(ret == -1) {
// 没有子进程
break;
}
}
}
int main() {
// 提前设置好阻塞信号集,阻塞SIGCHLD,因为有可能子进程很快结束,父进程还没有注册完信号捕捉
sigset_t set;
sigemptyset(&set);
sigaddset(&set, SIGCHLD);
sigprocmask(SIG_BLOCK, &set, NULL);
// 创建一些子进程
pid_t pid;
for(int i = 0; i < 20; i++) {
pid = fork();
if(pid == 0) {
break;
}
}
if(pid > 0) {
// 父进程
// 捕捉子进程死亡时发送的SIGCHLD信号
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("parent process pid : %d\n", getpid());
sleep(2);
}
} else if( pid == 0) {
// 子进程
printf("child process pid : %d\n", getpid());
}
return 0;
}
#include
#include
int shmget(key_t key, size_t size, int shmflg);
IPC_CREAT
IPC_EXCL
, 需要和IPC_CREAT一起使用IPC_CREAT | IPC_EXCL | 0664
void *shmat(int shmid, const void *shmaddr, int shmflg);
int shmdt(const void *shmaddr);
int shmctl(int shmid, int cmd, struct shmid_ds *buf);
key_t ftok(const char *pathname, int proj_id);
问题1:操作系统如何知道一块共享内存被多少个进程关联?
- 共享内存维护了一个结构体struct shmid_ds 这个结构体中有一个成员 shm_nattch
- shm_nattach 记录了关联的进程个数
问题2:可不可以对共享内存进行多次删除 shmctl
- 可以的
- 因为shmctl 标记删除共享内存,不是直接删除
- 什么时候真正删除呢?
当和共享内存关联的进程数为0的时候,就真正被删除
- 当共享内存的key为0的时候,表示共享内存被标记删除了
如果一个进程和共享内存取消关联,那么这个进程就不能继续操作这个共享内存。也不能进行关联。
共享内存和内存映射的区别
共享内存可以直接创建,内存映射需要磁盘文件(匿名映射除外)
共享内存效果更高
内存
所有的进程操作的是同一块共享内存。
内存映射,每个进程在自己的虚拟地址空间中有一个独立的内存。
数据安全
生命周期
/* 写一个守护进程 每隔两秒获取系统时间 */
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
void work(int num) {
//捕捉到信号后 ,获取系统时间 写入磁盘文件
//printf("抓取到信号编号:%d\n",num);
time_t tmm = time(NULL);
struct tm * loc = localtime(&tmm); //定义为现在时间
/* 写入数组的形式输出(未去重定向stdIn /OUT /ERR_FILE)才能看到*/
// char buf[1024];
// sprintf(buf,"%d-%d-%d %d:%d:%d",loc->tm_year,loc->tm_mon,loc->tm_mday,loc->tm_hour,loc->tm_min,loc->tm_sec);
// printf("%s\n",buf);
/*写入磁盘,重定向stdIn /OUT /ERR_FILE的情况下 在磁盘文件中看到,屏幕上看不到*/
char * ptr = asctime(loc); //返回时间信息
int fd = open("time.txt",O_RDWR|O_CREAT|O_APPEND,0664);
write(fd,ptr,strlen(ptr));
close(fd);
}
int main() {
//创建子进程
pid_t pid = fork();
//退出父进程
if(pid>0) exit(0);
//子进程调用setsid 创建会话(守护进程创建)
setsid();
//umask以确保守护进程创建文件和目录时有权限
umask(664); //不能写0664 必须写三位 要不会出错!
//修改当前进程目录 chdir
chdir("/home/yang/");
//关闭和重定向文件描述符
int fd = open("/dev/null",O_RDWR);
dup2(fd,STDERR_FILENO);
dup2(fd,STDIN_FILENO);
dup2(fd,STDOUT_FILENO);
//核心业务逻辑
//定时器计时完毕会发出SIGALARM 信号 需要去捕捉
struct sigaction act;
act.sa_flags = 0;
act.sa_handler = work;
sigaction(SIGALRM,&act,NULL);
//创建定时器
struct itimerval set;
//第一次开始 过2秒开始运行
set.it_interval.tv_sec = 2;
set.it_interval.tv_usec = 0;
//每隔两秒运行一次
set.it_value.tv_sec = 2;
set.it_value.tv_usec = 0;
setitimer(ITIMER_REAL,&set,NULL);
//不让进程结束,定时器才会持续工作
while(1) {
sleep(10);
}
return 0;
}