程序和进程
进程是正在运行的程序的实例,进程由用户内存空间和一系列内核数据结构组成
程序可以创建多个进程
单道程序,计算机内存只允许一个程序运行
多道程序 同时存在几道相互独立的程序
时间片,时间片是由操作系统内核调度程序分配给每个进程,内核会给每个进程分配相等的初始时间片,每个进程轮番执行相应的时间
并行同一个时刻,有多条指令在多个处理器上同时执行
并发统一时刻只能一条指令执行,多个进程指令被快速的轮换执行
进程管理块 PCB
进程ID 进程的状态 进程切换时需要保存恢复的cpu寄存器 描述虚拟地址空间的信息 描述控制终端的信息
进程状态转换
就绪态运行态阻塞态
进程相关命令
查看进程
ps aux/ajx
PPID父进程 PID PGID进程组 SID会话
实时显示进程的动态
top
杀死进程
进程创建
系统允许一个进程创建新进程,新进程是子进程,子进程还可以创建新的子进程形成进程树结构模型
/*
#include <sys/types.h>
#include <unistd.h>
pid_t fork(void);
//成功,返回值返回两次,子进程返回0,父进程返回子进程ID
在父进程中返回-1表示创建失败,设置errno
*/
#include <sys/types.h>
#include <unistd.h>
#include<stdio.h>
int main(){
//创建子进程
pid_t pid=fork();
//判断是父进程还是子进程
if(pid>0){
printf ("pid:%d\n",pid);
printf("i am parent pid : %d,ppid :%d",getpid(),getppid());
}
else if(pid==0){
printf("i am child pid : %d,ppid :%d",getpid(),getppid());
}
for (int i=0;i<5;i++){
printf("i:%d\n",i);
sleep(1);
}
return 0;
}
fork之后虚拟地址空间会被复制过来pid会变、
fork使用写时拷贝,内核此时并不复制整个进程的地址空间,而是共享
只需要在写入的时候才会复制地址空间,从而使各个拥有自己的地址空间
资源的复制在写入的时候进行,在此之前,只有以只读的方式共享
fork产生的子进程与父进程相同的文件文件描述符指向相同的文件夹,引用计数增加
父子进程的关系
fork函数返回值不同
PCB的一些数据,进程ID PID ,当前进程的父进程ID PPID 信号集
共同点 在某些状态下,子进程还没有被创建出来,还没有执行任何写数据的操作,
-用户区的数据-文件描述符表
父子进程对变量是不是共享
刚开始的时候是一样的如果修改了数据的,不共享了
读时共享(子进程被创建) 写时共享(子进程被创建,两个进程没有做任何写的操作
GDB调试
默认只能跟踪一个进程,默认跟踪父进程
exec函数族
exec函数族的作用时根据指定的文件名找到可执行文件,并用他来取代调用进程的内容,在调用进程内部执行一个可执行文件
执行成功之后不会返回,调用进程的实体已经被新的内容取代,只有调用失败,它们才会返回-1
#include <unistd.h>
extern char **environ;
int execl(const char *path, const char *arg, ...
参数Path需要指定执行的路径或者名称推荐绝对路径
arg 执行文件所需要的参数列表
第一个参数没有任何作用,为了方便,一般写的时执行程序的名称
第二个参数开始,程序执行所需要的参数列表
NULL结束
返回值
调用失败才有返回值-1 设置errno
成功没有返回值
*/
#include <unistd.h>
#include<stdio.h>
int main(){
pid_t pid=fork();
if(pid>0){
printf("i am parent pid:%d\n",getpid());
}else if (pid==0){
execl("hello","hello",NULL);//执行hello的内容
//execl("/bin/ps", "ps", "aux", NULL);
perror("execl");
printf("i am child process, pid : %d\n", getpid());//不会执行
}
for (int i=0;i<5;i++){
printf("i:%d,pid=%d\n",i,getpid());//父进程
sleep(1);
}
return 0;
}
i am parent pid:40808
i:0,pid=40808
hello, world
i:1,pid=40808
i:2,pid=40808
i:3,pid=40808
i:4,pid=40808
execlp会在环境变量中查找
execv argv是需要的参数的一个shi
char *argv[]={"ps","aux",NULL};
execv("/bin/ps",argv);
进程控制
status进程退出的状态信息,父进程回收子进程资源的时候可以查看
printf后面带\n,可以自己刷新IO缓冲区
没有/N,数据写在缓冲区里,系统调用_exit,所以不打印;但是exit会刷新,所以打印
孤儿进程
父进程运行结束,子进程还在运行。出现孤儿进程的时候,设置父进程为Init,ppid是1,会循环wait一斤推出的子进程。当一个孤儿进程结束生命周期的时候 ,init会收尾
僵尸进程
每个进程结束之后会释放自己用户区的数据,内核区的PCB没有办法自己释放,需要父进程自己的释放,进程结束之后需要父进程区释放。结束之后父进程尚未回收,PCB存放于内核中,编程僵尸进程,不能被KILL -9杀死。进程号会一直被占用,这是僵尸进程的危害
进程回收
每个进程退出的时候,内核释放该进程所有的资源,父进程可以调用,得到退出状态同时彻底清除wait waitpid功能一样,wait会阻塞,waitpid可以设置不阻塞,waitpid还可以指定等待哪个子进程结束,每次只能清理一个子进程,循环多次清理
管道读写特点
使用管道时,需要注意以下几种特殊的情况(假设阻塞I/O操作
1.所有指向管道写端的文件描述符都关闭了,管道写端引用计数为0,进程从管道的读端读数据,那么管道剩余的数据被读取之后,再次read会返回0,就像读到文件末尾一样
2.如果有指向管道写端的文件描述符没有关闭,管道写端引用计数大于0,而持有管道写也没有往管道写入数据,有时候有进程从管道读取数据,那么管道中剩余的数据被读取之后再次read会阻塞,直到管道有数据可以读了才读取数据并返回
3.如果所有指向管道读端的文件描述符被关闭了,管道读端引用计数大于0 ,这个时候有向管道中写入数据,那么该进程会收到一个信号SIGPIPE,通常会导致进程异常停止
4.如果有指向管道读端的文件描述符没有关闭,管道读端引用计数大于0,而持有管道的进程也没有在管道读数据,这时有进程向管道写入数据,那么在管道被写满的时候再次write直到管道有空位置才能再次写入数据并返回
总结
读管道
管道中有数据,read返回实际读到的字节数
管道无数据:
写端被全部关闭,read返回0,相当于读到文件的末尾
写端没有完全关闭,read会阻塞
写管道
管道读端全部被关闭,进程异常终止,进程收到SIGPIPE信号
管道读端没有全部关闭,
管道已满,write阻塞
管道没有满,write将数据写入,并返回实际写入的字节数
让读变成非阻塞
/*
#include <unistd.h>
int pipe(int pipefd[2]);
功能:创建一个匿名管道,用来进程间通信。
参数:int pipefd[2] 这个数组是一个传出参数。
pipefd[0] 对应的是管道的读端
pipefd[1] 对应的是管道的 写端
返回值:
成功 0
失败 -1
管道默认是阻塞的:如果管道中没有数据,read阻塞,如果管道满了,write阻塞
注意:匿名管道只能用于具有关系的进程之间的通信(父子进程,兄弟进程)
*/
/*
设置管道非阻塞
fcntl(fd[0],F_GETFL);
flags|=O_NONBLOCK;
fcntl(fd[0],F_SETFL,flags);
*/
// 子进程发送数据给父进程,父进程读取到数据输出
#include <unistd.h>
#include <sys/types.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <fcntl.h>
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);
flags|=O_NONBLOCK;
fcntl(pipefd[0],F_SETFL,flags);
while(1) {
int len = read(pipefd[0], buf, sizeof(buf));
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;
}
有名管道
提供了一个路径名与之关联,以FIFO文件的形式存在于文件系统,并且其打开方式与打开一个普通文件是一样的。与管道一样,FIFO也有一个写入端和读取段,并且从管道中读取数据的顺序与写入的顺序是一样的,FIFO的名称也由此而来,先入先出
不同点
1.FIFO作为一个特殊问价存在,FIFO中的内容却存放于内存中
2.当使用FIFO的进程退出后,FIFO文件继续保存在文件系统中以便以后使用
3.FIFO有名字,不相关的进程可以通过有名管道进行通信
使用
1.命令创建mkfifo
2.常见的文件I/O函数都可以用于FIFO,如close,read,write,
3.FIFO严格遵循先进先出,对管道及FIFO的读总是从开始处返回数据,对他们的写则是把数据添加到末尾,它们不支持lseek等文件定位操作
注意事项
//先打开写,输出管道不存在,创建管道,直到读端打开,才写入数据
当读端关闭,停止写入数据
读同理
#include <sys/types.h>
#include <sys/stat.h>
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<unistd.h>
#include <fcntl.h>
int main ()
{
//以只写的方式打开数据
int fd = open("test",O_RDONLY);
if(fd == -1){
perror("open");
exit(0);
}
while(1){
char buf[1024]={0};
int len =read(fd,buf,sizeof(buf));
if(len==0){
printf("写端断开\n");
break;
}
printf("recv buf : %s\n",buf);
}
close(fd);
return 0;
}
#include <sys/types.h>
#include <sys/stat.h>
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<unistd.h>
#include <fcntl.h>
int main ()
{//1.文件是否存在
int ret=access("test",F_OK);
if(ret==-1){
printf("管道不存在创建管道\n") ;
//创建管道
ret =mkfifo("test",0664);
if(ret==-1){
perror("mkfifo");
exit(0);
} }
//以只写的方式打开数据
int fd = open("test",O_WRONLY);
if(fd == -1){
perror("open");
exit(0);
}
//写数据
for(int i=0 ; i<100 ; i++){
char buf[1024];
sprintf(buf, "hello,%d\n", i);
printf("write data :%s\n", buf);
write(fd,buf,strlen(buf));
sleep(1);
}
close(fd);
return 0;
}
1.一个只读而打开一个管道的进程会阻塞,直到另一个进程为只写打开管道
2.一个只写而打开一个管道的进程会阻塞,直到另一个进程为只读打开管道
读管道
管道有数据,read返回实际读到的字节数
管道无数据:
写端被全部关闭,read返回0,相当于读到文件的末尾
写端没有完全关闭,read会阻塞
写管道
管道读端全部被关闭,进程异常终止,进程收到SIGPIPE信号
管道读端没有全部关闭,
管道已满,write阻塞
管道没有满,write将数据写入,并返回实际写入的字节数
用有名管道实现聊天的功能
进程A1.只写的方式打开fifo1 2.只读的方式打开fifo2 3.循环读写 获取键盘录入fgets数据写fifo1读fifo2
进程B 1.只读的方式打开fifo1 2.只写的方式打开fifo2 3.循环读写 读fifo1 获取 写fifo2
只能A一条B一条
内存映射
将磁盘文件的数据映射到内存当中,通过修改内存就能修改磁盘文件
进程通信
/*
#include <sys/mman.h>
void *mmap(void *addr, size_t length, int prot, int flags,
int fd, off_t offset);
将一个文件或者设备映射到内存当中
void *addr:NULL,有内核决定
length 要映射的数据的长度,这个值不能为0,,建议使用文件的长度
stat lseek获取
prot对申请的内存映射区的操作权限
PROT_NONE 没有权限
PROT_EXEC 可执行
PROT_READ 读
PROT_WRITE 写
要操作映射内存,必须要有读的权限,
读写一起可以按位或
flags
MAP_SHARED 映射区的数据会自动和磁盘文件同步,进程间通信,必须要有这个
MAP_PRIVATE 不同步,内存映射区的数据改变了,对原来的文件不会改变,会重新创建一个新的文件copy on write类似fork
fd文件描述符,文件大小不能为0,open指定的权限不能和Prot参数有冲突 Prot的权限小于等于open
offset偏移量,一般不用,4K的整数倍
0表示不偏移
返回值:返回内存的首地址
如果错误返回-1
int munmap(void *addr, size_t length);
释放内存映射
参数
addr:要释放的内存首地址
length:要释放的内存大小,要和mmap的length参数的值一样
*/
/*
使用内存映射实现进程通信
1,有关系的进程(父子进程
还没有子进程的时候
通过唯一的父进程,先创建内存映射区
有了内存映射区以后,创建子进程
父子进程共享创建的内存映射区
2.没有关系的进程
准备一个文件大小不是0 的磁盘文件
进程1 通过磁盘文件创建内存映射区
得到一个操作这块内存的指针
进程2 通过磁盘文件创建文件映射区
得到一个操作这块内存的指针
使用内存映射区通信
内存映射区通信非阻塞
*/
#include <stdio.h>
#include <sys/mman.h>
#include <fcntl.h>
#include <wait.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
int main (){
//1.打开一个文件
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);
}
//创建子进程
pid_t pid=fork();
if(pid>0){ char buf[64];
wait(NULL);
strcpy(buf,(char *)ptr);
printf("read data: %s\n",buf);
}else if(pid==0){
strcpy((char *)ptr,"nihao a ,son!!!");
}
munmap(ptr,size);
return 0;
}
//输出read data :nihao a ,son!!! Txt文件前面也加上这句话
注意事项
1.如果对mmap的返回值做++操作,munmap是否能成功
可以对其++操作,释放的时候错误,要保存地址
2.如果open的时O_RDONLY,mmap prot是读写
错误返回MAP_FAILED,建议一致
3.如果文件偏移量是1000
off,1024的整数倍,返回错误
4.mmap调用失败
第二个参数length=0;prot只有写权限,open和prot权限不一样
5.open的时候o_create创建一个新文件创建映射区
可以,文件大小不为0;对新的文件进行扩展,Lseek;truncate;
6.Mmap关闭文件描述符,对mmap映射有没有影响
映射区还存在,fd关闭没有影响
7.ptr越界操作有影响吗
越界操作的是非法内存——段错误
复制文件
/*
1.对原始的文件进行内存映射
2.新文件,扩展
3.新文件数据映射到内存
4.内存拷贝
5.释放资源
*/
#include <stdio.h>
#include <sys/mman.h>
#include <fcntl.h>
#include <wait.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
int main (){
int fd=open("english.txt",O_RDWR);
if(fd==-1){
perror("open");
exit(0);
}
int fd1=open("cpy.txt",O_RDWR|O_CREAT,0664);
if(fd1==-1){
perror("open");
exit(0);
}
int size=lseek(fd,0,SEEK_END);
truncate("cpy.txt",size);
void *ptr=mmap(NULL,size,PROT_READ|PROT_WRITE,MAP_SHARED,fd,0);
void *ptr1=mmap(NULL,size,PROT_READ|PROT_WRITE,MAP_SHARED,fd1,0);
if(ptr==MAP_FAILED){
perror("mmap");
exit(0);}
if(ptr1==MAP_FAILED){
perror("mmap");
exit(0);
}
memcpy(ptr1,ptr,size);
munmap(ptr,size);
munmap(ptr1,size);
close(fd);
close(fd1);
return 0;
}
信号
事件发生时对进程的通知机制,异步通信,在内核产生
1.对前台进程,用户特殊的终端符号来发送信号
2.硬件发生异常,或者引用无法访问的内存区域
3.系统状态发生变化
4.KILL命令,调用KILL函数
core文件,可以打开,查看错误信息
/*
#include <sys/types.h>
#include <signal.h>
给某个进程pid或者进程组发送某个信号
Pid>0指定的进程
-0当前的进程组
=-1 每一个有权限接受信号的进程
<-1 pid=某个进程组的id取反
sig宏值,0表示不发送任何信号
int kill(pid_t pid, int sig);
#include <signal.h>
int raise(int sig);
kill(getppid(),9);父进程、
kill(getpid(),9)
#include <signal.h>
功能给当前的进程发送信号
sig
返回值成功0 失败非0
int raise(int sig);
void abort(void)
发送SIGABRT信号给当前的进程,杀死当前进程
*/
#include <sys/types.h>
#include <sys/stat.h>
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<unistd.h>
#include <fcntl.h>
#include <signal.h>
int main ()
{
pid_t pid=fork();
if(pid==0){
int i=0;
for(i=0;i<5;i++){
printf("child process\n");
sleep(1);
}
}else if(pid>0){
printf("parent process\n");
sleep(2);
printf("kill child process\n");
kill(pid,SIGINT);
}
return 0;
}
/*
#include <unistd.h>
unsigned int alarm(unsigned int seconds);
设置定时器,函数调用开始倒计时,倒计时为0的时候,函数会给当前进程发送一个信号SIGALALRM
seconds倒计时的时长,单位秒参数为0 ,定时器无效
取消一个定时器alarm(0)
返回值:
之前有定时器,返回之前的倒计时剩余的时间
没有定时器,返回0
SIGALARM:默认终止当前的进程,每个进程都有有且唯一一个定时器
alarm(10)返回0,
过了一秒
alarm(5)返回9
alarm(100)函数时不阻塞
实际时间=内核时间+用户时间+消耗时间
进行文件IO操作的时候比较费时间
定时器和进程状态无关,无论进程什么状态都会计时
*/
#include <unistd.h>
#include<stdio.h>
int main(){
int seconds=alarm(5);
printf("seconds :%d\n",seconds);
sleep(2);
seconds=alarm(10);
printf("seconds :%d\n",seconds);
while(1){
}
return 0;
}
信号集
多个信号组成的集合,sigset_t
PCB有两个重要信号集 阻塞信号集,未决信号集 内核使用位图机制实现,操作系统不允许我们直接对信号集进行位操作,自定义另外一个集合,借助信号集操作函数对PCB的两个信号集进行修改
未决是一种状态,指的时从信号的产生到信号被处理前的一段时间
阻塞是一个开关动作,指的是组织信号被处理,但不是阻止信号产生
信号的阻塞就是让系统暂时保留信号留待以后发送,由于另外有办法让系统忽略信号,所以一般情况下信号的阻塞是暂时的,只是为了防止信号打破敏感的操作
阻塞和未决信号集
1.用户通过键盘ctrl+c,产生2号信号SIGINT(信号被创建
2.信号产生但是没有被处理(未决
-在内核将所有没有被处理的信号存储在一个 集合中
SIGINT信号状态被存储在第二个标志位上
标志位的值是0,说明信号不是未决状态
这个标志位是1,说明信号处于未决状态
3.这个未决状态的信号,需要被处理,处理之前需要和阻塞信号集比较,阻塞信号集默认不阻塞任何的信号,如果想要阻塞某些信号需要用户调用系统的API,阻塞信号的值是1,就被阻塞,是0的话,可以被处理
/*
对自定义信号集操作
int sigemptyset(sigset_t *set)
-功能:清空信号集中的数据,信号几种的所有标志位清为0
-参数:SET,传出参数,需要传出的信号集
-返回值:成功返回0,失败返回-1
int sigaddset(sigset_t *set,int signum)
-功能:设置信号集中的某一个信号对应的标志位为1,表示阻塞这个信号
-参数:SET,传出参数,需要传出的信号集
-signum需要设置阻塞的信号
-返回值:成功返回0,失败返回-1
int sigdelset(sigset_t *set,int signum)
-功能:设置信号集中的某一个信号对应的标志位为1,表示阻塞这个信号
-参数:SET,传出参数,需要传出的信号集
-signum需要设置不阻塞的信号
-返回值:成功返回0,失败返回-1
int sigimember(const sigset_t *set,int signum)
判断某个信号是否阻塞
signum需要判断的吸纳后
返回1,signum被阻塞
返回0,signum不阻塞
返回-1,调用失败
*/
#include <stdio.h>
#include <signal.h>
int main(){
//创建信号集
sigset_t set;
sigemptyset(set);
//sigint
int ret=sigimember(&set,SIGINT);
if(ret==0){
printf("SIGINT 不阻塞\n");
else if(ret==1)
printf("SIGINT 阻塞\n");
}
//添加
sigaddset(&set,SIGINT);
sigaddset(&set,SIGQUIT);
ret=sigimember(&set,SIGINT);
if(ret==0){
printf("SIGINT 不阻塞\n");
else if(ret==1)
printf("SIGINT 阻塞\n");
}
sigdelset(&set,SIGQUIT);
return 0;
}
/*
#include <signal.h>
int sigprocmask(int how, const sigset_t *set, sigset_t *oldset);
功能:就自定义信号集中的数据设置到内核信号集中,设置阻塞,解除阻塞,替换
参数
how:如何将内核阻塞信号集进行处理
SIG_BLOCK:将用户设置的阻塞信号集添加到内核中内核原来的数据不变
假设内核more阻塞信号集是mask,mask|set
SIG_UBLOCK:根据用户设置的数据,对内核中的数据进行解除阻塞
mask &=~set按位与反set
SIG_SETMASK:覆盖内核中原来的值
set:已经初始化的用户自定义的信号集
old_value:保存设置之前的内核中的内核阻塞信号集的状态,可以是NULL
返回值:
成功:0
失败:-1
设置错误号:EFAULT/EINVAL
int sigpending (sigset_t *set)
获取内核中的未决信号集
set 传出参数,保存内核中未决信号集的信息
编写一个程序,将所有的常规信号未决状态打印到屏幕
设置某些信号阻塞
*/
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
int main (){
//设置2 3 信号阻塞
sigset_t set;
sigemptyset(&set);
sigaddset(&set,SIGINT);
sigaddset(&set,SIGQUIT);
int num=0;
sigprocmask(SIG_BLOCK,&set,NULL);
while(1){
num++;
sigset_t pendingset;//获取未决
sigemptyset(&pendingset);
sigpending(&pendingset);
//遍历
for(int i=0;i<31;i++){
int ret =sigismember(&pendingset,i);
if(ret==1){
printf("1");
}else if(ret==0){
printf("0");
}
else{
perror("sigismember");
exit(0);
}
}
printf("\n");
sleep(1);
if(num==10){
sigprocmask(SIG_UBLOCK,&set,NULL);
}
}
return 0;
}
/* #include <signal.h>
int sigaction(int signum, const struct sigaction *act,
struct sigaction *oldact);
检查或者设置信号的处理,信号捕捉
-signum:信号编号
-act:捕捉信号之后的处理动作
-oldact:NULL
返回值
struct sigaction {
函数指针,指向的函数就是信号捕捉之后的处理函数
void (*sa_handler)(int);
不常用
void (*sa_sigaction)(int, siginfo_t *, void *);
临时阻塞信号集,在信号捕捉函数执行过程中,临时阻塞某些信号
sigset_t sa_mask;
使用哪一个信号处理对捕捉的信号进行处理
这个值可以是0,表示使用sa_handler,也可以是SA_SIGINFO,表示使用sigaction
int sa_flags;
不常用
void (*sa_restorer)(void);
*/
#include <stdio.h>
#include <sys/mman.h>
#include <fcntl.h>
#include <wait.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/time.h>
void myalarm(int num){
printf("捕捉到了信号的编号是%d\n",num);
printf("xxxxxxx\n");
}
int main(){
//注册信号捕捉
struct sigaction act;
act.sa_flags=0;//用sa_handler处理
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);
}
while(1);
return 0;
}
sigaction常用,signal不常用
系统有一个阻塞信号集,在信号捕捉的时候会使用临时的阻塞信号集,处理完之后会恢复到阻塞信号集
多次发送某个信号,只会处理当前一个,其他的信号会阻塞在那
未决信号和阻塞信号集只支持一个标志位
/*
条件
子进程结束,暂停,继续运行
解决僵尸进程
*/
#include <stdio.h>
#include <sys/mman.h>
#include <fcntl.h>
#include <wait.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/time.h>
void myfun (int num){
printf("捕捉到的信号%d\n",num);
//回收子进程的位置
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 <sys/ipc.h>
#include <sys/shm.h>
int shmget(key_t key, size_t size, int shmflg);
功能创建一个新的共享内存端,或者获取一个既有的共享内存段的标识
新创建的内存段中的数据都会被初始化为0
参数
-Key key_t类型是一个整形,通过这个或者找到创建一个共享内粗
16进程 非0
- size 共享内存的大小
- shmflg属性
-访问权限
-附加属性,创建、判断共享内存是不是存在
创建IPC_CREAT
判断共享内存是否存在,IPC_EXCL,需要和IPC_CREAT一起使用
IPC_CREAT|IPC_EXCL|0064
-返回值失败-1,成功》0,返回共享引用的ID,后面操作只用
void *shmat(int shmid, const void *shmaddr, int shmflg);
-功能和当前的进程guanlian
- shmid共享内存的标识Id
- shmaddr申请的共享内存的起始地址,NULL内核指定
- shmflg对共享内存的操作
SHM_RDONLY必须要有读权限
读写:0
- 返回值
成功返回首地址
失败(void *)-1
void *shmat(int shmid, const void *shmaddr, int shmflg);
-功能:解除当前进程和共享内存的关联
-参数
shmaddr首地址
-返回值,成功0,失败-1
int shmctl(int shmid, int cmd, struct shmid_ds *buf);
-删除共享内存,共享内存要删除才消失,创建共享内存的链接就会消失
-shmidid
cmd要做的操作
IPC_STAT 获取共享内存的当前的状态
IPC_SET 设置共享内存的状态
IPC_RMID 标记共享内存被销毁
-buf 需要设置或者获取的共享内存属性信息
IPC_STAT buf存储数据
IPC_SET buf需要初始化数据,设置到内核中
IPC_RMID 没用NULL
key_t ftok(const char *pathname,int proj_id);
根据指定的路径名和Int值,生成一个共享内存的Key
pathname路径
proj_id int这系统调用只使用其中的一个字节
范围0-255 一般指定一个字符'a'
*/
//写数据
#include<stdio.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include<string.h>
int main() {
// 1.获取一个共享内存
int shmid = shmget(100, 0, IPC_CREAT);
printf("shmid : %d\n", shmid);
// 2.和当前进程进行关联
void * ptr = shmat(shmid, NULL, 0);
// 3.读数据
printf("%s\n", (char *)ptr);
printf("按任意键继续\n");
getchar();
// 4.解除关联
shmdt(ptr);
// 5.删除共享内存
shmctl(shmid, IPC_RMID, NULL);
return 0;
}
#include<stdio.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include<string.h>
int main (){
//1.获取共享内存
int shmid=shmget(100,0,IPC_CREAT);
printf("shmid:%d\n",shmid);
//关联
void *ptr=shmat(shmid,NULL,0);
//3.读数据
printf("%s\n",(char *)ptr);
//4.解除关联
printf("按任意键继续\n");
getchar();
shmdt(ptr);
//5.删除共享内存
shmctl(shmid,IPC_RMID,NULL);
return 0;
}
1.操作系统如何知道共享内存被多少进程关联
共享内存维护了一个结构体streuct shmid_ds,这个结构体有个成员shm_nattach记录了关联的进程个数
2.可以对共享内存进行多次删除shmctl
可以,因为shmctl标记删除共享内存,不是直接删除
什么时候真正删除
当共享内训关联的进程数为0的时候,就被真正删除
当共享内存key为0,不爱书共享内存标记删除了
如果一个进程和共享内存取消关联,那么这个进程就不能继续操作这个共享内存
3.共享内存和内存映射的区别
-共享内存可以直接创建,内存映射需要哦磁盘文件,匿名映射除外
-共享内存效率更高
-内存
所有的进程操作的是同一块共享内存
内存映射,每个进程在自己的虚拟地址空间有一个独立的内存
-数据安全
进程突然退出
共享内存还存在,内存映射小时
运行进程的电脑司机
数据存在在共享内存没有了,
内存映射区的数据,由于磁盘文件数据旱灾,所以内存映射区的数据还在’
-生命周期
内存映射区,进程退出,内存映射区销毁
共享内存:进程退出,共享捏u才能还在,标记删除(所有的关联进程数为0 ,或者关机
如果一个进程退出,会自动和共享内存取消关联
守护进程
bash 去创建find进程组,wc是find子进程,ppid是父进程,pgid是进程组编号,sid是会话组,加了 &,在后端执行
创建sort,uniq是子进程,默认在前端执行,前端只能有一个进程组,享有控制终端
1.获取进程的组2.获取指定的进程组Id3.设置进程组5.设置会话Id
比如父进程id100,组id100,会话Id是100,子进程id100,不是当前会话id
子进程创建新会话,新会话Id是101,与之前会话不冲突没有控制终端
写数据到
/*
写一个守护进程,每隔2s获取一下系统时间,将这个时间写u人到磁盘文件中
*/
#include <stdio.h>
#include <sys/stat.h>
#include <sys/mman.h>
#include <sys/types.h>
#include <fcntl.h>
#include <wait.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/time.h>
#include<time.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);
char *str =asctime(loc);
int fd=open("time.txt",O_RDWR|O_CREAT,0664);
write(fd,str,strlen(str));
close(fd);
}
int main (){
pid_t pid=fork();
if(pid>0){
exit(0);
}
setsid();
umask(022);
chdir("/home");
int fd=open("/dev/null",O_RDWR);
dup2(fd,STDIN_FILENO);
dup2(fd,STDOUT_FILENO);
dup2(fd,STDERR_FILENO);
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);
while(1){
sleep(10);
}
return 0;
}
/dev/null,会丢弃