复习:
进程 :
父子进程
孤儿进程
僵尸进程
进程的状态:
运行态
阻塞态
暂停态
僵死态
消亡态
命令:
ps aux 查看进程号
ps -ef 查看进程号
top 查看cpu,内存占用率
pstree linux倒立的树形结构
bg 1 使程序在后台运行
fg 1 使程序在前台运行
ctrl + c 终止进程
ctrl + z 暂停进程
ctrl + \
函数:
getpid();获取子进程的pid getppid();获取父进程的pid
fork(); 创建子进程
exit(); 清理io缓存区并退出 _exit();退出,但是不清理io缓存区
wait(&t);收尸函数,&t存储子进程的返回状态 waitpid(pid,&t,WNOHANG);收尸函数,&t存储子进程pid的返回状态,WNOHANG
不阻塞,0阻塞。
execl();第二个参数以列表的形式写出 execv();第二个参数传递为指针数组 execvp();第一个参数为文件名
指针数组; 是数组。 int *a[3]; |int *|int *|int *| *a[0]
数组指针: 是指针。 int (*p)[4];
守护进程:
1. fork(); 父进程: exit(0);使父进程脱离终端的控制
2. setsid(); 创建新会话。 脱离进程组,原会话组的控制
3. chdir("/tmp");//创建目录,灵活操作
4. umask(0);掩码为0,保留子进程继承父进程的权限
5. close(0/1/2); 关闭文件描述符。 关闭标准输入,标准输出,标准错误输出
多进程:
int main()
{
int i;
for(i=0; i<3; i++)
{
fork();
}
}
i=0; 父 子1
i=1; 子2 子1-1
i=2; 子3 子1-2 子2-1 子1-1-1
进程间通信: 信号、 管道、 共享内存、消息队列、信号量、 socket套接字
一、 信号
1. 什么是信号: 在 windows 中,如果无法结束一个进程,可以通过任务处理器结束。
同样的功能,在linux和unix中,使用信号这种机制来处理。
一个信号的产生叫生成、信号的接收叫捕获。
信号:是进程间通信机制中唯一的异步通信机制。
2. 信号的种类: man 7 signal (kill -l)
Signal Value Action Comment
──────────────────────────
SIGINT 2 Term ctrl + c 中断信号
SIGQUIT 3 Core ctrl + \ 终止进程信号
SIGILL 4 Core 非法指令
SIGABRT 6 Core abort 函数产生的终止信号
SIGFPE 8 Core 浮点异常
SIGKILL 9 Term 必杀
SIGSEGV 11 Core 段错误 -- 地址非法使用
SIGPIPE 13 Term 管道破裂
SIGALRM 14 Term 闹钟信号
SIGTERM 15 Term kill 产生的信号
SIGCHLD 20,17,18 Ign 子进程终止后,会向父进程发送此信号
SIGTSTP 18,20,24 Stop ctrl + z 将一个运行着的进程扔到后台,并暂停
3. 信号处理方式
1) 捕获信号 signal(SIGINT,fun); /当SIGINT 信号被捕获,执行 fun 函数。
2) 忽略信号 signal(SIGTSTP,SIG_IGN);
3) 默认处理 signal(SIGILL,SIG_DFL);
4. 处理信号的函数: signal
函数原型: typedef void (*sighandler_t)(int); // 将 sighandler_t 这个指针,typedef 成为一个类型:函数指针类型。
sighandler_t signal(int signum, sighandler_t handler);
头文件: #include <signal.h>
参数: 参数一: 指定信号代码。
参数二: 对于信号的处理方式。
返回值: 函数指针
功能: 对信号做处理。 信号处理函数。
说明: typedef void (*sighandler_t)(int);
void (*sighandler_t)(int); 这是一个函数指针的定义。 定义的指针名为 sighandler_t.
当这个定义前,加上 typedef ,这个指针就不再是一个指针,而是重定义成类型名了。
所以得到的结论是: sighandler_t 是一个类型名。 可以用它来定义此种类型的变量。
示例:
#include <stdio.h>
#include <signal.h>
void fun(int sig_num)
{
switch(sig_num)
{
case SIGINT:
printf("lalala ~\n");
break;
case SIGTSTP:
printf("hello world!\n");
break;
case SIGQUIT:
printf("I'm a teacher\n");
break;
default:
break;
}
return;
}
int main()
{
signal(SIGINT,fun);
signal(SIGTSTP,fun);
signal(SIGQUIT,fun);
while(1);
return 0;
}
//练习:编写程序 向数组中输入数据 当收到信号(SIGINT)时 统计出数组中元素的个数 sleep(1);
#include <stdio.h>
#include <signal.h>
int count = 0;
void fun(int sig_num)
{
printf("count : %d\n",count+1);
return ;
}
int main()
{
signal(SIGINT,fun);
int a[1000] = {0};
for(count=0; count<1000; count++)
{
a[count] = 66;
sleep(1);
}
return 0;
}
5. raise
函数原型: int raise(int sig);
参数: 信号的编号。
功能: 只能给自己发送信号。
返回值: 成功 0
失败 -1
6. 发送信号的函数: kill
函数原型: int kill(pid_t pid, int sig);
头文件: #include <sys/types.h>
#include <signal.h>
参数; 参数一: pid > 0 发送信号给 进程号为 pid 的这个进程。
pid == 0 发送信号给进程组的所有进程。
pid == -1 发送信号给除了init 的所有进程。
pid < -1 发送信号给进程组号为 -pid 的每一个进程。
参数二: sig: 信号类型。
返回值: 成功: 0
失败: -1
示例:
文件1:sig.c
#include <stdio.h>
#include <signal.h>
void fun(int sig_num)
{
printf("I'm not afraid of you\n");
return;
}
int main()
{
printf("%d\n",getpid());
signal(SIGINT,fun);
while(1);
return 0;
}
//文件2: kill.c
#include <stdio.h>
#include <sys/types.h>
#include <signal.h>
int main(int argc,char *argv[])
{
int pid = atoi(argv[1]); //将传递过来的字符串转换为整型。
kill(pid,SIGINT);
return 0;
}
//
执行过程:
1) gcc sig.c
2) gcc kill.c -o mykill
3) ./a.out //等信号
4) ./mykill 15678 //给进程号为 15678 的这个进程发送信号。
7. 闹钟信号函数: alarm
函数原型: unsigned int alarm(unsigned int seconds);
头文件: #include <unistd.h>
参数: seconds 无符号整型的秒数。 经过 seconds 秒,发送 SIGALRM 信号。
返回值: 如果之前设置过时间,本次会返回上次设置时间后,剩余的秒数。
如果之前没有设置过时间,则返回 0 。
示例:
///
#include <stdio.h>
#include <unistd.h>
#include <signal.h>
#include <sys/types.h>
void fun(int s)
{
printf("Time out !\n");
return ;
}
int main()
{
signal(SIGALRM,fun);
alarm(5);
int i;
for(i=0; i<10; i++)
{
printf("Hello\n");
sleep(1);
}
return 0;
}
//练习:测试分钟打字速度
1 随机产生字符 rand()%26+'a'
2 signal(SIGALRM,fun)
//
#include <signal.h>
#include <time.h>
#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>
int count = 0;
void fun(int signum)
{
printf("Your speed is %d\n",count*3);
exit(0);
}
int main()
{
srand(time(0));
signal(SIGALRM,fun);
alarm(20);
char c,input;
while(1)
{
c = rand()%26 + 'a';
printf("%c\n",c);
input = getchar();
getchar();
if(input == c)
count ++;
}
}
管道
二、 无名管道
1. 创建管道函数: pipe
描述符:int pfd[2]; pfd[0]:读端 pfd[1]:写端
函数原型: int pipe(int pipefd[2]);
头文件: #include <unistd.h>
参数: 文件描述符。
返回值: 成功: 0
失败: -1
特点:
1) 半双工通信
2) 无名管道,只能用在有亲缘关系的进程之间。
3) 不是文件。不属于任何文件系统,只存在于内存中。
4) 只要管道中信息被读取,被读取的内存就释放。可以继续存放数据。
5) 管道传输信息时,没有类型。 所以双方在读写时,要事先约定好类型。
示例:
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
int main()
{
int pfd[2];
int ret = pipe(pfd);
if(ret < 0)
{
perror("pipe");
exit(-1);
}
pid_t id = fork();
if(id < 0)
{
perror("fork");
exit(-1);
}
else if(id == 0)
{
sleep(2);
close(pfd[1]);
int n;
read(pfd[0],&n,sizeof(n));
close(pfd[0]);
printf("%d \n",n);
}
else
{
close(pfd[0]);
int num;
puts("input a num:");
scanf("%d",&num);
write(pfd[1],&num,sizeof(num));
close(pfd[1]);
wait(NULL);
}
return 0;
}
//练习:父进程从键盘获取一个字符串 传递给子进程
子进程 收到串后 转为大写 在输出到屏幕
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
void change(char *p)
{
while(*p) //直到字符串结束。
{
if(*p >= 'a' && *p <= 'z') //如果在小写字母范围。 换成大写 。
*p = *p - 'a' + 'A';
p++;
}
return ;
}
int main()
{
int pfd[2];
int ret = pipe(pfd); //先创建管道。
if(ret < 0)
{
perror("pipe");
exit(-1);
}
pid_t id = fork(); //然后创建进程。
if(id < 0)
{
perror("fork");
exit(-1);
}
else if(id == 0) //子进程
{
sleep(2);
close(pfd[1]); //关闭写端。
char string[100] = "\0";
read(pfd[0],string,sizeof(string)); //读取信息到变量中。
close(pfd[0]);
change(string); //大小写转换。
printf("%s \n",string); //输出最终结果。
}
else
{
close(pfd[0]); //关闭读端。
char buf[100] = {0};
puts("input a string:");
gets(buf);
write(pfd[1],buf,sizeof(buf)); //向管道文件中写内容。
wait(NULL);
}
return 0;
}
三 有名管道:
1. 创建有名管道: mkfifo
函数原型: int mkfifo(const char *pathname, mode_t mode);
头文件: #include <sys/types.h>
#include <sys/stat.h>
参数: 参数一: 管道文件所在路径,一直写到管道文件名称。
参数二: 管道文件的权限。
返回值: 成功: 0
失败: -1
2. 删除文件: unlink
函数原型: int unlink(const char *pathname);
头文件: #include <unistd.h>
参数: pathname : 管道文件所在路径,一直要写到管道文件名字结束。
返回值: 成功: 0
失败: -1
3. 步骤:
1) mkfifo
2) open
3) read/write
4) close
5) unlink
4. 与无名管道的区别:
a. 有名管道是个管道文件。
b. 有名管道可以用在任意两个进程之间。这两个进程不必有亲缘关系。
示例:
write.c
//
#include "my.h"
int main()
{
int ret = mkfifo("./myfifo",0664);
if(ret < 0)
{
perror("mkfifo");
exit(-1);
}
int fd = open("./myfifo",O_WRONLY);
if(fd < 0)
{
perror("open");
exit(-1);
}
int n = 122;
write(fd,&n,sizeof(n));
close(fd);
return 0;
}
read.c
///
#include "my.h"
int main()
{
int fd = open("./myfifo",O_RDONLY);
if(fd < 0)
{
perror("open");
exit(-1);
}
int n;
read(fd,&n,sizeof(n));
printf("n = %d \n",n);
close(fd);
unlink("./myfifo");
return 0;
}
///
执行过程:
1) gcc write.c -o w
2) gcc read.c -o r
3) ./w (有可能在阻塞等待)
4) ./r 读取文件信息。
//练习:使用有名管道,进行进程间通信。 进程1从键盘获取一个字符串 传递给子进程
进程2 收到串后 转为大写 在输出到屏幕
三个文件:
create.c (功能:创建有名管道)
//
#include <stdio.h>
int main(int argc,char *argv[])
{
if(argc < 2)
{
printf("%s fifo_name\n",argv[0]);
return 0;
}
int ret = mkfifo(argv[1],0664);
if(ret < 0)
{
perror("mkfifo");
return -1;
}
printf("OK\n");
return 0;
}
write.c
///
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
int main(int argc,char *argv[])
{
if(argc < 2)
{
printf("%s <fifo_name>\n",argv[0]);
return -1;
}
int fd = open(argv[1],O_WRONLY);
char buf[100] = {0};
puts("input a string:");
gets(buf);
write(fd,buf,sizeof(buf));
close(fd);
return 0;
}
read.c
//
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
void change(char *p)
{
while(*p)
{
if(*p >= 'a' && *p <= 'z')
*p = *p - 'a' + 'A';
p++;
}
return ;
}
int main(int argc,char *argv[])
{
if(argc < 2)
{
printf("%s <fifo_name>\n",argv[0]);
return -1;
}
int fd = open(argv[1],O_RDONLY);
char string[100] = "\0";
read(fd,string,sizeof(string));
close(fd);
change(string);
printf("%s \n",string);
return 0;
}
//
执行过程:
gcc create.c -o creat
gcc write.c -o w
gcc read.c -o r
./creat fifo
./w fifo (写入数据,如果空间不足,阻塞写)
./r fifo 读取数据。
//练习: 进程1,向管道中写一个学生信息: struct worker,进程2 读信息。
struct worker
{
char name[30];
int age;
float salary;
};
answer: 4 files
(1) my.h
/
#ifndef _MY_H_
#define _MY_H_ 1
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
typedef struct{
char name[30];
int age;
float salary;
}worker_t;
#endif /* _MY_H_ */
(2)
create.c
//
#include <stdio.h>
int main(int argc,char *argv[])
{
if(argc < 2)
{
printf("%s fifo_name\n",argv[0]);
return 0;
}
int ret = mkfifo(argv[1],0664);
if(ret < 0)
{
perror("mkfifo");
return -1;
}
printf("OK\n");
return 0;
}
(3) write.c
/
#include "my.h"
int main(int argc,char *argv[])
{
if(argc < 2)
{
printf("%s <fifo_name>\n",argv[0]);
return -1;
}
int fd = open(argv[1],O_WRONLY);
worker_t w;
puts("input your name age salary:");
scanf("%s%d%f",w.name,&w.age,&w.salary);
write(fd,&w,sizeof(w));
close(fd);
return 0;
}
(4) read.c
//
#include "my.h"
int main(int argc,char *argv[])
{
if(argc < 2)
{
printf("%s <fifo_name>\n",argv[0]);
return -1;
}
int fd = open(argv[1],O_RDONLY);
worker_t w;
read(fd,&w,sizeof(w));
close(fd);
unlink(argv[1]);
printf("%s :--> %d %.2f \n",w.name,w.age,w.salary);
return 0;
}
//
执行过程:
gcc create.c -o creat
gcc write.c -o write
gcc read.c -o read
./creat myfifo
./write myfifo
./read myfifo
//作业:实现有名管道版本的cat:
write.c 将磁盘文件中的数据 传递给进程read.c
read.c 将文件内容输出到屏幕