前言:学习笔记。随时修改完善。
目录
一、进程的基本概念
1、概念
正在运行的程序叫做进程。
进程是操作系统对于正在运行的程序的一种抽象。
操作系统为了执行程序,而分配的资源的总称。
进程是资源分配的基本单位,也是独立运行的基本单位。
2、组成
代码段
数据段
系统数据段:pcb/寄存器/堆栈
pcb:存放进程的属性
进程的标识PID。
进程用户(标识进程由哪个用户创建)
进程的状态,优先级
文件描述符
3、分类
a.交互进程:该类进程由shell控制和运行。该类进程既可在前台运行,也可在后台运行。
b.批处理进程:该类进程不属于某个终端,它被提交到一个序列中,以便顺序执行。
c.守护进程:该类进程在后台运行。
4、状态
R:运行态或者就绪态
T:暂停态
S:可中断等待态
D:不可中断等待态
Z:死亡态
二、进程的系统调用
1、命令
1)ps:查看进程
2)top:动态显示进程
3)nice:按用户指定的优先级运行程序
4)renice:改变正在运行的进程的优先级
5)kill:结束进程,包括后台进程
6)bg:将挂起的进程在后台执行
7)fg:把后台运行的进程放到前台运行
2、系统调用
1)fork()函数
fork函数用于创建一个子进程,该子进程几乎拷贝了父进程的全部内容。
a.头文件
#include <sys/types.h>
#include <unistd.h>
b.函数原型
pid_t fork(void);
c.函数功能
创建子进程
d.返回值
>0:父进程返回子进程的PID
=0:子进程返回0
-1:error,并设置error的值
注意:
1、fork成功后,会建立一个子进程,子进程和父进程有独立的地址空间。
2、子进程会拷贝父进程几乎所有的内容。
3、父子进程执行的顺序不确定,统一参与cpu的调度。
4、子进程结束时,必须由父进程回收子进程的资源。
2)exec函数族
exec函数族提供了一种在进程中启动另一个程序执行的方法。
a.exec函数
int execl(const char *pathname, const char *arg, ...
/* (char *) NULL */);
int execlp(const char *file, const char *arg, ...
/* (char *) NULL */);
int execv(const char *pathname, char *const argv[]);
int execvp(const char *file, char *const argv[]);
b.使用
execvp()为例,执行ls -l的功能
#include<stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/wait.h>
int main()
{
pid_t pid = fork();//创建子进程
if(-1 == pid)
{
perror("fork");
return -1;
}
if(0 == pid)//子进程
{
//execl("/bin/ls", "ls","-l",NULL);//执行指定目录下的文件,例如: /bin下的 ls -l
char *argv[] = {"ls","-l",NULL};
// execv("/bin/ls", argv);//执行ls -l
//execlp("ls","ls","-l",NULL);//默认目录下(/bin)找ls并执行
execvp("ls",argv);
exit(0);
}
else if(pid > 0)//父进程
{
wait(NULL);
printf("I am parent process.\n");
exit(0);
}
return 0;
}
3)wait()
进程一旦调用了wait,就立即阻塞自己,由wait自动分析是否当前进程的某个子进程已经退出,如果让它找到了这样一个已经变成僵尸的子进程,wait就会收集这个子进程的信息,并把它彻底销毁后返回;如果没有找到这样一个子进程,wait就会一直阻塞在这里,直到有一个出现为止。
a.头文件
#include <sys/types.h>
#include <sys/wait.h>
b.函数原型
pid_t wait(int *wstatus);
c.函数功能
回收子进程的资源
d.返回值
成功,wait会返回被收集的子进程的进程ID。
如果调用进程没有子进程,调用就会失败,此时wait返回-1,同时errno被置为ECHILD。
e.代码
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/wait.h>
int main(int argc, char *argv[])
{
pid_t pid = fork();//创建子进程
if(-1 == pid)//出错处理
{
perror("fork");
return -1;
}
if(0 == pid)//子进程
{
printf("I am child process.My PID is %d.My parent PID is %d\n",getpid(),getppid());
exit(0);
}
else//父进程
{
wait(NULL);//阻塞等任意子进程结束,并回收子进程产生的资源
printf("I am parent process.My PID is %d.My parent PID is %d\n",getpid(),getppid());
}
exit(0);
return 0;
}
运行结果
f.(4
)WIFEXITED、WIFSIGNALED、WEXITSTATUS这几个宏用来获取子进程的退出状态。
WIFEXITED
宏用来判断子进程是否正常终止(return、exit、_exit退出)WIFSIGNALED
宏用来判断子进程是否非正常终止(被信号所终止)WEXITSTATUS
宏用来得到正常终止情况下的进程返回值的。
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/wait.h>
int main(int argc, char *argv[])
{
pid_t pid = fork();//创建子进程
if(-1 == pid)
{
perror("fork");
return -1;
}
if(0 == pid)
{
printf("I am child process.My PID is %d.My parent PID is %d\n",getpid(),getppid());
exit(0);//结束子进程
}
else//父进程
{
int status;
wait(&status);
printf("exit status:%d\n",WEXITSTATUS(status));//返回子程序的退出状态
printf("end status:%d\n",WIFEXITED(status));//获取子进程是否正常结束
printf("end number:%d\n",WTERMSIG(status));//返回导致子进程终止的信号编码
// while(waitpid(pid,NULL,WNOHANG) == 0)
printf("I am parent process.My PID is %d.My parent PID is %d\n",getpid(),getppid());
}
exit(0);
return 0;
}
运行结果:
4)waitpid()
(1) 基本功能和wait一样,都是用来回收子进程
(2) waitpid可以回收指定PID
的子进程
(3) waitpid可以阻塞式或非阻塞式两种工作模式
a.函数原型
pid_t waitpid(pid_t pid, int *wstatus, int options);
b.函数参数
wstatus:用来接收子进程返回的状态值
options:WNOHANG(非阻塞等待);0(阻塞式等待,等价wait(NULL).)
pid:
<-1:表示等待进程组ID等于pid绝对值的子进程。
-1:表示等待任何子进程。0:
表示等待进程组号与调用进程组号相等的子进程。
>0:表示等待进程号等于pid值的子进程。
c.代码
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/wait.h>
int main(int argc, char *argv[])
{
pid_t pid = fork();//创建子进程
if(-1 == pid)//出错处理
{
perror("fork");
return -1;
}
if(0 == pid)//子进程
{
printf("I am child process.My PID is %d.My parent PID is %d\n",getpid(),getppid());
exit(0);
}
else//父进程
{
waitpid(pid,NULL,WNOHANG);//非阻塞回收进程号为pid的子进程结束并回收子进程的资源
printf("I am parent process.My PID is %d.My parent PID is %d\n",getpid(),getppid());
}
exit(0);
return 0;
}
运行结果:
三、进程间的通信(重点)
1、分类
1)早期的进程间通信
pipe:无名管道
fifo:有名管道
信号:
2)System V的IPC通信
消息队列:
共享内存:效率最高的通信方式
信号灯集:
3)BSD:套接字通信
socket(网络)
2、pipe无名管道
1)特点
a.只能用于具有亲缘关系的进程之间的通信。
b.半双工的通信模式,具有固定的读端和写端。
c.管道可以看成是一种特殊的文件,对于它的读写可以使用文件IO如read、write函数。
d.无名管道并不是普通的文件,不属于任何文件系统,并且只存在于内存中。
2)函数
#include <unistd.h>
int pipe(int pipefd[2]);
3)管道的创建与关闭
管道是基于文件描述符的通信方式。当一个管道建立时,它会创建两个文件描述符fd[0]和fd[1]。其中fd[0]固定用于读管道,而fd[1]固定用于写管道。构成了一个半双工的通道。
4)无名管道的使用
#include<stdio.h>
#include <sys/stat.h>
#include <sys/wait.h>
#include <unistd.h>
#define N 64
int main()
{
int fd[2];
int ret = pipe(fd);//创建无名管道
if(-1 == ret)
{
perror("pipe");
return -1;
}
pid_t pid = fork();//创建子进程
if(-1 == pid)
{
perror("fork");
return -1;
}
if(0 == pid)//子进程
{
char buf[] = "I am child process.";
close(fd[0]);//关闭读端
if(-1 == write(fd[1],buf,sizeof(buf)))//写管道
{
perror("write");
close(fd[1]);
exit(-1);
}
close(fd[1]);
exit(0);
}
else //父进程
{
char buf[N] = {0};
close(fd[1]);//关闭写端
wait(NULL);
if(-1 == read(fd[0],buf,sizeof(buf)))//读管道
{
perror("read");
close(fd[0]);
exit(-1);
}
printf("buf:%s\n",buf);
close(fd[0]);
exit(0);
}
return 0;
}
运行结果:
3、fifo有名管道
1)特点
a.有名管道可以使互不相关的两个进程互相通信。有名管道可以通过路径名来指出,并且在文件系统中可见
b.进程通过文件IO来操作有名管道c.有名管道遵循先进先出规则
d.不支持如lseek() 操作
2)函数
int mkfifo(const char*filename,mode_t mode)
filename:要创建的管道
mode:指定创建的管道的访问权限,一般用8进制数表示
3)有名管道的创建
#include<stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
int main()
{
int ret = mkfifo("myfifo",0777);//创建有名管道
if(-1 == ret)
{
perror("mkfifo");
return -1;
}
printf("mkfifo success.\n");
return 0;
}
运行结果:
4)通过fifo实现读写
writer.c
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>
#define N 64
int main(int argc, char *argv[])
{
char buf[N]={0};
int fd = open("myfifo",O_WRONLY);
if(-1 == fd)
{
perror("open");
return -1;
}
do
{
fgets(buf,sizeof(buf)-1,stdin);//读标准输入
write(fd,buf,sizeof(buf));
}while(strncmp(buf,"quit",4) != 0);
close(fd);
return 0;
}
reader.c
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>
#define N 64
int main(int argc, char *argv[])
{
char buf[N]={0};
int fd = open("myfifo",O_RDONLY);
if(-1 == fd)
{
perror("open");
return -1;
}
while(1)
{
int ret = read(fd,buf,sizeof(buf));//
if(-1 == ret)
{
perror("read");
close(fd);
return -1;
}
if(strncmp(buf,"quit",4) == 0)
break;
printf("%s\n",buf);
}
close(fd);
return 0;
}
编译成可执行文件:
打开两个终端,运行:
5)注意
1)当管道中没有数据时,读管道阻塞
2)当写端关闭时,读端读到0
3)当读端关闭时,写端继续写,就会造成管道破裂,系统发送信号SIGPIPE信号给写进程
4、信号
1)概念
a.信号是在软件层次上对中断机制的一种模拟,是一种异步通信方式.
b.主要用于用户进程和内核进程进行交互.
2)用户进程对信号的响应方式
1)忽略信号:对信号不做任何处理,但是有两个信号不能忽略:即SIGKILL及SIGSTOP。
2)捕捉信号:定义信号处理函数,当信号发生时,执行相应的处理函数。3)执行默认操作:Linux对每种信号都规定了默认操作
3)常见信号
SIGKILL: 终止进程
SIGALRM:闹钟信号 当定时器时间到时就会收到此信号
SIGSTOP: 暂停
SIGCHLD:子进程结束时父进程收到此信号, 默认:忽略
SIGPIPE:管道破裂 默认:终止
4)signal()函数
功能:将信号和处理函数关联
#include <signal.h>
typedef void (*sighandler_t)(int);//函数声明
sighandler_t signal(int signum, sighandler_t handler);
5)signal()函数的使用
#include<stdio.h>
#include <unistd.h>
#include <signal.h>
void handler(int signo)//回调函数
{
if(signo == SIGALRM)
printf("time out.\n");
}
int main()
{
signal(SIGALRM,handler);//信号和处理函数关联
printf("%d\n",alarm(2));//返回上个alarm剩余的时间
pause();//将进程挂起 直到收到信号
while(1)
{
printf("hello\n");
sleep(1);
}
return 0;
}
运行结果:
5、共享内存
共享内存是一种最为高效的进程间通信方式,进程可以直接读写内存,而不需要任何数
据的拷贝。
1)shmget()函数
a.头文件
#include <sys/ipc.h>
#include <sys/shm.h>
b.函数原型
int shmget(key_t key, size_t size, int shmflg);
函数参数:
key: IPC_PRIVATE 或 ftok()获取
size:共享内存大小
shmflg: IPC_CREAT 不存在则创建
c.函数功能
创建/打开共享内存
d.返回值
成功:共享内存的id.
失败:-1 设置errno.
2)ftok()函数
a.头文件
#include <sys/types.h>
#include <sys/ipc.h>
b.函数原型
key_t ftok(const char *pathname, int proj_id);
c.函数参数
const char *pathname:文件路径名
int proj_id:子序号
d.返回值
成功:返回生成的key值
失败:返回-1
3)shmat()函数
共享内存映射到私有地址空间
a.头文件
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/shm.h>
b.函数原型
void *shmat(int shmid, const void *shmaddr, int shmflg);
c.函数参数
shmid: shmget的返回值
shmaddr:NULL 系统自己去映射到私有地址空间
shmflg: 0 共享内存可读可写
d.返回值
成功:映射的首地址。
失败:(void *)-1,设置errno.
4)shmdt()函数
解除映射。
a.头文件
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/shm.h>
b.函数原型
int shmdt(const void *shmaddr);
c.函数参数
shmaddr:shmat的返回值。
d.返回值
成功:0
失败:-1 设置errno
5)shmctl()函数
删除共享内存
a.头文件
#include <sys/ipc.h>
#include <sys/shm.h>
b.函数原型
int shmctl(int shmid, int cmd, struct shmid_ds *buf);
c.函数参数
shmid: shmget的返回值
cmd:IPC_RMID 删除共享内存
buf: NULL
d.返回值
成功:0
失败:-1 设置errno
6)共享内存的实现
a.创建/打开共享内存。shmget
b.映射共享内存,即把指定的共享内存映射到进程的地址空间用于访问。shmat
c.撤销共享内存映射。shmdt
d.删除共享内存对象。shmctl
7)代码
共享内存实现读和写。
writer.c
#include<stdio.h>
#include <sys/shm.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <string.h>
#include <unistd.h>
#define SIZE 64
int main()
{
key_t key = ftok(".",'s');//获取key
if(-1 == key)
{
perror("ftok");
return -1;
}
//1.创建共享内存 若存在则打开
int shmid = shmget(key,SIZE,IPC_CREAT | 0777);
if(-1 == shmid)
{
perror("shmget");
return -1;
}
printf("shmid=%d\n",shmid);
//2.共享内存映射
char *p = (char *)shmat(shmid,NULL,0);
if((char *)-1 == p)
{
perror("shmat");
return -1;
}
//循环读标准输入,并将读取的内容写入共享内存,直到输入"quit"结束
char buf[SIZE] = {0};
do{
fgets(buf,sizeof(buf)-1,stdin);
buf[strlen(buf)-1] = '\0';//处理'\n'
strcpy(p, buf);//写共享内存
}while(strncmp(buf,"quit",4) != 0);
//3.解映射
if(-1 == shmdt(p))
{
perror("shmdt");
return -1;
}
return 0;
}
reader.c
#include<stdio.h>
#include <sys/shm.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <string.h>
#include <unistd.h>
#define SIZE 64
int main()
{
key_t key = ftok(".",'s');//获取key
if(-1 == key)
{
perror("ftok");
return -1;
}
//1.创建共享内存 若存在则打开
int shmid = shmget(key,SIZE,IPC_CREAT | 0777);
if(-1 == shmid)
{
perror("shmget");
return -1;
}
printf("shmid=%d\n",shmid);
//2.共享内存映射
char *p = (char *)shmat(shmid,NULL,0);
if((char *)-1 == p)
{
perror("shmat");
return -1;
}
//循环读标准输入,并将读取的内容写入共享内存,直到输入"quit"结束
while(1)
{
puts(p);//读共享内存
if(strncmp(p,"quit",4) == 0)
break;
sleep(1);
}
//3.解映射
if(-1 == shmdt(p))
{
perror("shmdt");
return -1;
}
//4.删除共享内存
if(-1 == shmctl(shmid,IPC_RMID,NULL))
{
perror("shmctl");
return -1;
}
return 0;
}
编译成可执行文件,打开两个终端运行
reader会一直打印共享内存里的内容,未在writer端输入内容时,共享内存没有内容,所以一直换行,直到writer端输入内容。
6、消息队列
1.步骤
msgget();//创建或打开消息队列
msgsnd();//添加消息到消息队列
msgrcv();//读取消息从消息队列
msgctl();//控制消息队列
7、信号灯
1.System V IPC 的信号灯
指:一个或多个信号灯的集合;
2.函数
semget();
semop();
semctl();