一、进程的概念
1.什么是程序:
存放在磁盘上的指令和数据的有序集合(文件)是静态的
2.什么是进程 :
进程是程序的一次执行过程,是动态的包括创建,调度,执行和消亡
3.进程内容:
系统数据包括:进程控制块(进程PID进程号,进程用户,状态,优先级,文件描述符表)CPU寄存器(进程调度,实现宏观上的并发),堆栈
4.进程类型:
交互进程 批处理进程 守护进程
5.进程状态
运行态 等待态 可中断 不可中断 停止态 死亡态
二、查看进程信息
1.ps 查看系统进程快照 2.top 查看进程动态信息 3. ./proc 文件查看
三 、进程相关命令
1.nice 按用户优先级运行进程
3.renice 改变正在运行的进程优先级 4.jobs 查看后台进程 5.bg 将挂起的进程在后台运行 6.fg 把后台运行的进程放到前台运行
四、进程相关命令
1.进程的创建-----fork()
创建信的进程,失败是返回-1; 成功是父进程返回进程的进程号,子进程返回0; 通过返回值区分父子进程;
#include<stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/types.h>
#include <unistd.h>
int main ()
{
pid_t pid;
if((pid = fork()) < 0){
perror("fork");
exit(-1);
}else if(pid == 0){
printf("child process: my pid = %d\n",getpid());
}else{
printf("parent process: my pid = %d\n",getpid());
}
return 0;
}
2.父子进程的关系
子进程继承了父进程的内容 父子进程有独立的地址空间,互不影响 若父进程先结束: 子进程成为孤儿进程,被init进程收养 ,子进程变成后台进程 若子进程先结束:父进程没有及时回收子进程变成僵尸态进程. 子进程从何时开始运行? 从fork下一条语句开始运行 父子进程谁先执行? 内核调度,都有可能先运行.
3.进程结束 exit/_exit
void exit(int status); void _exit(int staus); 结束当前进程并将status返回 exit结束进程时回刷新缓冲区
int main ()
{
printf("hello");
exit(0);
printf("word");
}
运行结果:hello
int main ()
{
printf("hello");
_exit(0);
printf("word");
}
运行结果:没有任何显示.
4.exec 函数族
进程调用exec 函数族执行某个程序 进程当前的内容被指定程序替换 实现让父子进程执行不同的程序: 1.父进程创建子进程 2.子进程调用exec函数族 3.父进程不受影响 (shell终端)
① execl/execlp
int execl(const char *path,const char *arg,…); const *path : 执行的程序名称,包含路径; const char arg :传递给执行程序的参数列表,最后一个传NULL 成功返回指定程序;失败返回EOF int execlp(const charfile,const char *arg,…); const *file : 执行的程序名称,不包含路径; 在PATH中查找 const char *arg :传递给执行程序的参数列表,最后一个传NULL 成功返回指定程序;失败返回EOF
② execv/execvp
int execv(const char *path,char *const argv[],…); 对比:execl 只是把执行程序的参数换成字符指针数组 int execvp(const char *file,char *const argv[],…); 对比:execlp只是把执行程序的参数换成字符指针数组 比上面的更灵活
③system
int system (const char *command);
成功返回命令commamd 的返回值;失败返回EOF 自动创建子进程. 当前的进程等待commamd 进程结束才继续运行.
5.进程回收
①pid_t wait(int *status);
pid_t wait(int *status); 成功返回子进程进程号,失败返回EOF 若子进程没有结束,父进程一直阻塞 若多个子进程,哪个先结束就先回收. status 指定保存子进程返回值的结束方式 status为NULL标识直接释放子进程PCB,不接收返回值 子进程通过exit/_exit/return 返回某个值(0-255); 父进程调用wait(&status)回收 WIFEXITED(status) 判断进程是否正常结束 WEXITSTATUS(status) 获取子进程返回值 WIFSIGNALED(status) 判断子进程是否被信号结束 WTERMSIG(status); 获取结束子进程的信号类型
② pid_t waitpid(pid_t pid ,int *status,int option);
pid_t waitpid(pid_t pid ,int *status,int option); 成功返回回收的子进程的pid或者0(子进程还没结束),失败返回EOF pid 可用于指定回收哪个子进程或者任意子进程(传-1); status指定用于保存子进程返回值和结束方式的地址 option 指定回收方式 0(阻塞方式) 或者WNOHANG(非阻塞)
五、守护进程
通常在系统启动是运行,系统关闭时结束 守护进程特点:始终在后台运行,独立于任何终端(避免终端关闭的时候守护进程退出),周期性的执行某种任务或者等待处理特定的事件.
1.守护进程的创建
1.创建子进程,父进程退出
if(fork()>0){
exit(0);
}
子进程变成孤儿进程,被init 进程收养 子进程在后台运行 2.子进程创建新会话
if(setsid()<0){
exit(-1);
}
子进程成为新的会话组组长 子进程脱离原先的终端 3.更改当前进程的工作目录
chdir("/");
chdir("/tmp");
守护进程一直在后台运行,其工作目录不能被卸载 4.重设文件权限掩码
if(umask(0)<0){
exit(-1);
}
5.关闭打开的文件描述符
int i;
for(i=0;i<gettablesize();i++){
close(i);
}
关闭所有从父进程继承的打开文件 已经脱离终端, stdin/stout/stderr无法再使用
创建守护进程,每隔一秒向time.log里写人系统时间
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
int main()
{
pid_t pid;
FILE *fp;
time_t t;
int i;
if((pid = fork())<0){
perror("fork");
exit(-1);
}else if(pid>0){
exit(0);
}
setsid();
umask(0);
chdir("/tmp");
for(i=0;i<getdtablesize();i++){
close(i);
}
if((fp = fopen("time.log","a")) == NULL){
perror("fopen");
exit(-1);
}
while(1){
time(&t);
fprintf(fp,"%s",ctime(&t));
fflush(fp);
sleep(1);
}
return 0;
}
六、进程间通信
1.无名管道(pipe)
无名管道的特点: 只能用于具有亲缘关系 的进程之间的通信(无名管道文件系统不可见,非亲缘关系的不认识此文件描述符) 单工(不能同时写和读)的通信模式,具有固定的读端和写端 无名管道创建是会返回两个文件描述符,分别用于读写管道
无名管道的创建:
int pipe(int pfd[2]);
成功返回0,失败返回-1; pfd:包含两个元素的整形数组,用于保存文件描述符 pfd[0] 读管道 pfd[1] 写管道 实现子进程一和子进程二分别写消息,父进程读
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <stdlib.h>
int main ()
{
char buf[32];
pid_t pid1,pid2;
int pfd[2];
if(pipe(pfd) <0){
perror("pipe");
exit(-1);
}
if((pid1 = fork())<0){
perror("fork:");
exit(-1);
}else if(pid1 == 0){
strcpy(buf,"I'm proess1");
write(pfd[1],buf,32);
exit(0);
}
if((pid2 = fork())<0){
perror("fork:");
exit(-1);
}else if(pid2 == 0){
sleep(1);
strcpy(buf,"I'm proess2");
write(pfd[1],buf,32);
exit(0);
}else{
wait(NULL);
read(pfd[0],buf,32);
printf("%s\n",buf);
wait(NULL);
read(pfd[0],buf,32);
printf("%s\n",buf);
}
return 0;
}
管道的读写特性
读无名管道: 1.写端存在 有数据 read返回实际读取的字节数 无数据 进程读堵塞 2.写端不存在 有数据 read返回实际读取的字节数 无数据 read返回0 写无名管道 1.读端存在 有空间 write返回实际写入的字节数 无空间 写进程堵塞 2.读端不存在 管道断裂(被信号结束进程);
2.有名管道(fifo)
有名管道的特点: 任意进程间的通信 打开管道时可以指定读写方式 通过文件IO,内容存放在内存中
有名管道的创建
int mkfifo(const char *path,mode_t mode);
成功返回0失败返回-1; path :创建的管道文件的路径 mode: 管道文件的权限,如0666; 例: 进程1 循环往有名管道里输入,输入quit时候退出 进程2 读管道并且输出到终端上
创建管道:
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <string.h>
#include <stdlib.h>
int main()
{
if(mkfifo("myfifo",0666)<0){
perror("mkfifo:");
exit(-1);
}
return 0;
}
写管道:
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <string.h>
#include <stdlib.h>
#include <fcntl.h>
#define N 32
int main()
{
int fd;
char buf[N];
if((fd=open("myfifo",O_WRONLY))<0){
perror("open:");
exit(-1);
}
printf("write open success\n");
while(1){
fgets(buf,N,stdin);
if(strcmp(buf,"quit\n") == 0) break;
write(fd,buf,N);
}
close(fd);
return 0;
}
读管道:
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <string.h>
#include <stdlib.h>
#include <fcntl.h>
#define N 32
int main()
{
int fd;
char buf[N];
if((fd=open("myfifo",O_RDONLY))<0){
perror("open:");
exit(-1);
}
printf("read open success\n");
while(read(fd,buf,N)>0){
printf("From write:%s\n",buf);
}
close(fd);
return 0;
}
3.信号(signal)
信号是在软件层次上对中断机制的一种模拟,是一种一部通信方式.
常用信号
信号相关命令 kill/killall
kill [-singnal] pid
默认发送SIGTERM
killall [-u user|prog]
prog :进程名字
-u user :指定用户的所有进程发信号
信号发送 kill/raise
int kill(pid_t pid,int sig);
int raise(int sig);
int kill(pid_t pid,int sig); 成功返回0,失败返回-1; pid : 接收进程的进程号;0代表同组进程;-1代表所有进程 sig:信号类型 int raise(int sig); 给本进程发送信号; 定时器:
int alarm(unsigned int seconds);
成功返回上个定时器的剩余时间,失败返回-1 seconds:定时器时间 一个进程只能设定一个定时器,时间到时产生SIGALRM信号
int pause(void);
进程一直阻塞,知道被信号中断 被信号中断返回-1;
#include <stdio.h>
#include <unistd.h>
int main()
{
alarm(3);
pause();
printf("hello world\n");
return 0;
}
运行结果
设置信号的响应方式 signal
void (*signal(int signo,void (*handler)(int)))(int);
成功返回原先的信号处理函数,失败返回SIG_ERR signo :要捕捉的信号 handler :信号处理函数 :SIG_DFL代表缺省方式,SIG_IGN代表忽略信号;
捕捉信号:
#include<stdio.h>
#include<signal.h>
#include <unistd.h>
void handler(int signo)
{
if(signo == SIGINT){
printf("From singal: SIGINT\n");
}else if(signo == SIGQUIT){
printf("From singal: SIGQUIT\n");
}
}
int main ()
{
signal(SIGINT,handler);
signal(SIGQUIT,handler);
while(1) pause();
return 0;
}
高级信号量
int sigaction(int signum, const struct sigaction *act,struct sigaction *oldact);
struct sigaction {
void (*sa_handler)(int);
void (*sa_sigaction)(int, siginfo_t *, void *);
sigset_t sa_mask; 默认阻塞
int sa_flags; 接受数据 SA_SIGINFO
void (*sa_restorer)(void);
};
int sigqueue(pid_t pid, int sig, const union sigval value);
pid
const union sigval value
union sigval {
int sival_int;
void *sival_ptr;
};
接收信号:
#include <stdio.h>
#include <signal.h>
void handler(int signum, siginfo_t *info, void * context)
{
printf("sifnum=%d\n",signum);
if(context!=NULL){
printf("form data=%d\n",info->si_int);
printf("form data=%d\n",info->si_value.sival_int);
}
}
int main ()
{
struct sigaction act;
act.sa_sigaction=handler;
act.sa_flags=SA_SIGINFO;
printf("pid=%d\n",getpid());
sigaction(SIGUSR1,&act,NULL);
while(1);
return 0;
}
发出信号:
#include <stdio.h>
#include <signal.h>
int main (int argc,char **argv)
{
int pid;
int signum;
signum=atoi(argv[1]);
pid=atoi(argv[2]);
union sigval value;
value.sival_int=101;
sigqueue(pid,signum,value);
printf("done\n");
return 0;
}
4.共享内存
key_t ftok(const char *path,int proj_id);
成功返回合法的key值,失败返回-1;
path:存在且课访问的文件的路径
proj_id:用于生成key的值,不能是0;
共享内存的特点
共享内存是一种最为高效的进程间通信方式,进程可以直接读写内存,而不需要任何数据的拷贝 共享内存在内核空间创建,可被进程映射到用户空间访问,使用灵活 由于多个进程可同时访问共享内存,因此需要同步和互斥机制互相配合使用
共享内存的使用步骤
1.创建/打开共享内存
int shmget(key_t key,int size,int shmfig);
成功返回共享内存的ID,失败返回-1;
key :key和共享内存关联的key,IPC_PRIVATE(私有的共享内存)或ftok生成
size;共享内存的大小;
shmfig:共享内存标志位 IPC_CREAT|0666;
2.映射共享内存(把指定的共享内存映射到进程的地址空间)
void *shmat(int shm_id, const void *addr, int flag);
成功返回映射后的地址
shm_id:要映射的共享内存ID
addr:映射后的地址,NULL表示系统自动映射
flag:标志位0表示可读可写;SHM_RDONLY表示只读
3.读写共享内存 通过指针访问共享内存;
#include <stdio.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <string.h>
int main ()
{
key_t key;
key=ftok(".",2);
char *sh;
int shmid=shmget(key,1024*4,0);
if(shmid==-1){
printf("shmget NO\n");
}
sh=shmat(shmid,0,0);
printf("From write=%s\n",sh);
shmdt(sh);
return 0;
}
#include <stdio.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <string.h>
int main ()
{
key_t key;
key=ftok(".",2);
char *sh;
int shmid=shmget(key,1024*4,IPC_CREAT|0666);
if(shmid==-1){
printf("shmget NO\n");
}
sh=shmat(shmid,0,0);
strcpy(sh,"hello world");
sleep(5);
shmdt(sh);
shmctl(shmid,IPC_RMID,0);
return 0;
}
4.撤销共享内存映射
int shmdt(void *addr);
成功返回0,失败返回-1
addr:映射后的地址
5.删除共享内存
int shmctl(int shm_id, int cmd, struct shmid_ds *buf);
成功返回0,失败返回-1
共享内存注意事项
共享内存是有大小个数的限制的; ipc -l
5.消息队列
消息队列有消息队列ID来唯一标识; 消息队列就是一个消息的列表,用户可以在消息队列中添加消息,读取消息等 消息队列可以按照类型来发送或者接收信息
消息队列的使用步骤
1.打开/创建消息队列
int msgget(key_t key,int msgfig);
成功返回消息队列的ID,失败返回-1;
key和消息队列关联的key IPC_PRIVATE或者ftok
msgfig :标志位,IPC_CREAT|0666
2.向消息队列发送消息
int msgsnd(int msgid, const void *msgp, size_t size, int msgflg)
成功:0
出错:-1,错误原因存于error中
msgid:消息队列标识符
msgp:消息缓冲区的地址
size:消息正文长度;
msgflg:0:当消息队列满时,msgsnd将会阻塞,直到消息能写进消息队列,IPC_NOWAIT:当消息队列已满的时候,msgsnd函数不等待立即返回
3.从消息队列接收消息
ssize_t msgrcv(int msqid, void *msgp, size_t msgsz, long msgtyp, int msgflg);
成功返回收到的消息长度,失败返回-1;
msqid:消息队列的ID
msgp:消息缓存区地址;
msgsz :指定接收的消息长度;
msgtyp:指定接收的消息类型
0:接收第一个消息
>0:接收类型等于msgtyp的第一个消息
<0:接收类型等于或者小于msgtyp绝对值的第一个消息
msgflg:标志位0: 阻塞式接收消息,没有该类型的消息msgrcv函数一直阻塞等待IPC_NOWAIT:如果没有返回条件的消息调用立即返回,此时错误码为ENOMSG
4.控制消息队列
int msgctl(int msqid, int cmd, struct msqid_ds *buf)
成功:0出错:-1,错误原因存于error中
cmd 要执行的操作:IPC_STAT IPC_SET IPC_SETIPC_RMID;
buf:存放 消息队列属性的地址;
实现A,B进程轮流给对方发消息:
#include<stdio.h>
#include<string.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#include <stdlib.h>
typedef struct
{
long mtype;
char msg[64];
}MSG;
#define LEN (sizeof(MSG) -sizeof(long))
#define TypeA 100
#define TypeB 200
int main ()
{
key_t key;
int msgid;
MSG buf;
if((key = ftok(".",2))<0){
perror("ftok");
exit(-1);
}
if((msgid = msgget(key,IPC_CREAT|0666))<0){
perror("msgget");
exit(-1);
}
while(1){
buf.mtype = TypeB;
printf("Iput >> ");
fgets(buf.msg,64,stdin);
msgsnd(msgid,&buf,LEN,0);
if(msgrcv(msgid,&buf,LEN,TypeA,0)<0){
perror("msgrcv");
exit(-1);
}
printf("From B : %s\n",buf.msg);
}
return 0;
}
#include<stdio.h>
#include<string.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#include <stdlib.h>
typedef struct
{
long mtype;
char msg[64];
}MSG;
#define LEN (sizeof(MSG) -sizeof(long))
#define TypeA 100
#define TypeB 200
int main ()
{
key_t key;
int msgid;
MSG buf;
if((key = ftok(".",2))<0){
perror("ftok");
exit(-1);
}
if((msgid = msgget(key,IPC_CREAT|0666))<0){
perror("msgget");
exit(-1);
}
while(1){
if(msgrcv(msgid,&buf,LEN,TypeB,0)<0){
perror("msgrcv");
exit(-1);
}
printf("From A : %s\n",buf.msg);
buf.mtype = TypeA;
printf("Iput >> ");
fgets(buf.msg,64,stdin);
msgsnd(msgid,&buf,LEN,0);
}
return 0;
}
6.信号灯集
可用是操作集合中的信号灯 避免死锁; 1.打开/创建信号灯
int semget(key_t key, int nsems, int semflg);
成功返回信号灯的ID失败返回-1 ; key和信号灯的key IPC_PRIVATE或者ftok nsems:集合中包含的计数信号灯的个数 semflg :标志位,IPC_CREAT|0666 ; IPC_EXCL(检查对象是否已经存在);
2.信号灯初始化
int semctl(int semid, int semnum, int cmd, ...);
semid:要操作的信号灯集合ID
semnum:要操作的集合中的信号灯编号
cmd:执行的操作 SETVAL
union semun :取决于cmd
union semun {
int val;
struct semid_ds *buf;
unsigned short *array;
struct seminfo *__buf;
};
3.P/V操作
int semop (int semid,struct sembuf *sops,unsigned nsops);
成功返回0,失败返回-1;
semid:要操作的信号灯集ID
sops:描述对信号灯操作的结构体(数组)
nsops : 要操作的信号灯的个数
struct sembuf
{
short sem_num;
short sem_op;
short sem_flg;
}
sem_num:信号灯编号
sem_op:-1:P操作 1:V操作
sem_flg:0(阻塞模式)IPC_NOWAIT(立刻返回);
父进程从键盘输入字符串到共享内存
子进程删除字符串中的空格并打印
父进程输入quit后删除共享内存和信号灯集,程序结束
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<string.h>
#include<signal.h>
#include<sys/types.h>
#include<sys/ipc.h>
#include<sys/shm.h>
#include<sys/sem.h>
#define N 64
#define READ 0
#define WRITE 1
union semun
{
int val;
struct semid_ds *buf;
unsigned short *array;
struct seminfo *__buf;
};
void init_sem(int semid ,int m[] ,int n)
{
union semun mu;
int i;
for(i=0;i<n;i++){
mu.val = m[i];
semctl(semid,i,SETVAL,mu);
}
}
void PV(int semid,int num,int op)
{
struct sembuf buf;
buf.sem_num = num;
buf.sem_op = op;
buf.sem_flg = 0;
semop(semid,&buf,1);
}
int main()
{
int semid,shmid, m[]={0,1};
char *addr;
pid_t pid;
key_t key;
if((key = ftok(".",'a'))<0){
perror("ftok");
exit(-1);
}
if((shmid = shmget(key,N,IPC_CREAT|0666))<0){
perror("shmget");
exit(-1);
}
if((semid = semget(key,2,IPC_CREAT|0666))<0)
{
perror("semget");
goto _error1;
}
init_sem(semid,m,2);
if((addr = (char *)shmat(shmid,NULL,0)) == (char *)-1){
perror("shmat");
goto _error2;
}
if((pid = fork())<0){
perror("fork");
goto _error2;
}else if(pid == 0){
char *p,*q;
while( 1 ){
PV(semid,READ,-1);
p = q = addr;
while( *p ){
if( *p != ' '){
*q++ = *p;
}
p++;
}
*q = '\0';
printf("%s",addr);
PV(semid,WRITE,1);
}
}else{
while( 1 ){
PV(semid,WRITE,-1);
printf("Iput >>");
fgets(addr,N,stdin);
if(strcmp(addr,"quit\n") == 0 ) break;
PV(semid,READ,1);
}
kill(pid,SIGUSR1);
}
_error2:
semctl(semid,0,IPC_RMID);
_error1:
shmctl(shmid,IPC_RMID,NULL);
return 0;
}
运行结果:
Iput >>aa ddad
aaddad
Iput >>ddd aa dd dd dd
dddaadddddd
Iput >>quit
|