进程基本原理和概念
进程:
进程ID:标识进程的唯一数字父进程的ID(PPID),启动进程的用户ID(UID)
进程互斥:若干进程都要使用某一共享资源时,任何时刻最多允许一个进程使用,其他要使用必须 等待,直到占用资源被释放。
临界资源:操作系统中将一次只允许一个进程访问的资源称为临界资源。
临界区:进程中访问临界资源的那段程序代码空间称为临界区。需要保证进程互赤的进入各自的临界区。
进程同步:一组并发进程按照一定的顺序执行的过程称为进程间的同步。
进程调度:按一定的短发,从一组待运行的进程中选出一个来占有CPU运行。{抢占式、非抢占式两种,1.先来先服务调度;2.端进程优先调度;3.高优先级先服务调度;4.时间片轮转法调度;}
死锁:多个进程因竞争资源而形成一种僵局,若无外力作用,这些进程将永远不能再向前推进。
— 进程控制编程—
-
获取进程ID
进程相关的头文件
#include<sys/types.h> #include<unistd.h>
a. 获取本进程ID
pip_t getpid(void)
b. 获取父进程ID
pip_t getppid(void)
实例:getid.c
#include<stdio.h> #include<unistd.h> #include <stdlib.h> int main(void) { printf("PID = %d \n",getpid()); printf("PPID = %d \n",getppid()); return 0; }
-
进程创建-fork
进程相关的头文件
/3include<sys/types.h> #include<unistd.h>
a.创建子进程
pip_t fork(void)
在父进程中,fork返回新创建的子进程的PID;
在子进程中,fork返回0;
如果出现一个错误,fork返回一个负值。
实例:fork1.c
#include<sys/types.h> #include<unistd.h> void main(void) { pid_t pid; /×此时仅仅只有一个进程×/ pid = fork(); /×此时同时运行两个进程×/ if(pid < 0) printf("eeror in fork !"); else if(pid == 0) printf("I'm the child process,ID is %d \n",getpid()); else printf("I'm the parent process,ID is %d \n",getpid()) }
子进程的数据空间、堆栈空间都会从父进程得到一个拷贝,而不是共享。在子进程中对数据进行单独处理,并且不会影响父进程的数据。
b.创建子进程
pip_t vfork(void)
fork 与 vfork 的比较
1.fork:子进程拷贝父进程的数据段
vfork:子进程与父进程共享数据段
2.fork:父子进程执行的次序不确定
vfork:子进程先行运行,父进程后运行
实例:vfork.c
#include<unistd.h> #include<stdio.h> int main() { pid_t pid; int count= 0; pid = vfork(); /*父进程、子进程共用代码段*/ count++; printf("count = %d \n",count); /*父进程、子进程共用代码段*/ return 0; }
b.创建子进程
int exec(const char* path,const char* arg1,…)
**** exec启动一个新的程序,而替换原有的进程,因而不会改变进程PID。****
path:被执行的程序名称(包含完整的路径)。
arg1-argn:被执行程序所需要的参数,包含程序名。以空指针NULL结束。
例子:execl.c
#include<unistd.h> void main() { execl("/bin/ls","ls","-al","/etc/passwd",(char*)0); }
int execlp(const char* path,const char* arg1,…)
path:被执行程序名称(不包含路径,将从path环境变量中查询到该程序)。
arg1~argn:被执行程序所需要的参数,包含程序名。以空指针NULL结束。
例子:execlp.c
#include<unistd.h> void main() { execlp("ls","ls","-al","/etc/passwd",(char*)0); }
int execv(const char* path,char* const argv[])
path:被执行程序名称(不包含路径,将从path环境变量中查询到该程序)。
argv[]:被执行程序所需要的参数的数组集合,包含程序名。以空指针NULL结束。
例子:execv.c
#include<unistd.h> void main() { char* argv[]={"ls","-al","/etc/passwd",(char*)0}; execv("/bin/ls",argv); }
int system(const char* string)
调用fork产生子进程,由子进程来调用程序,执行工作。
头文件: #include<stdlib.h>
例子:system.c
#include<stdlib.h> void main() { system("ls -al /etc/passwd"); }
-
进程等待
进程相关的头文件
#include<sys/types.h> #include<sys/wait.h> #include<unistd.h> #include<stdlib.h>
a.进程等待
pip_t wait(int * status)
例子:wait.c
#include<sys/types.h> #include<sys/wait.h> #include<unistd.h> #include<stdlib.h> void main() { pid_t pc,pr; pc = fork(); if(pc == 0){ printf("this is child process with pid of %d \n",getpid()); sleep(10); } else if(pc > 0){ pr = wait(NULL); printf("I catched a child process with pid of %d \n",pr); } exit(0); }
— 进程通信设计—
-
无名管道(管道)pipe
ps:管道是单向的,先进先出的规则,连接一个进程的输出和另外一个进程的输入,一个进程在管道的尾部写入数据,另外一个进程从管道的头部读出数据。无名管道只能用于父子进程之间的通信!无名管道由pipe()函数创建。
int pipe(int filedis[2])
当管道建立之后,会创建两个文件描述符,filedis[0]用于读管道; filedis[1]用于写管道。
注意::一定要在系统调用fork函数之前调用pipe函数来创建管道。否则子进程将不会继承文件描述符。
例子:pipe.c
#include<unistd.h> #include<errno.h> #include<stdio.h> #include<stdlib.h> int main() { int pid_fd[2]; if(pipe(pipe_fd) < 0) { printf("pipe creat error\n"); return -1; } else printf("pipe creat success \n"); close(pipe_fd[0]);// 关闭读(头部) close(pipe_ed[1]);//关闭写(尾部) }
pipe_rw.c
#include<unistd.h> #include<sys/types.h> #include<errno.h> #include<stdio.h> #include<stdlib.h> int main() { int pipe_fd[2]; pid_t pid; char buf_r[100]; char * p_wbuf; int r_num; memset(buf_r,0,sizeof(buf_r)); //创建通信管道 if(pipe(pipe_fd)<0){ printf("pipe creat error \n"); return -1; } //创建子进程 if((pid = fork())==0){ printf("\n"); close(pipe_fd[1]); sleep(2);//睡眠2秒 if((r_num = read(pipe_fd[0],buf_r,100))>0) printf("%d numbers from the pipe is %s \n"); close(pipe_fd[0]); exit(0); } else if(pid > 0){ close(pipe_fd[0]); if(write(pipe_fd[1],"hello",5)!=-1) printf("parent write 1 hello!\n"); if(write(pipe_fd[1],"pipe",5)!=-1) printf("parent write 2 pipe!\n"); close(pipe_fd[1]); sleep(3); waitpid(pid,NULL,0);//等待子进程结束 exit(0); } ]
-
有名管道 FIFO
相关的头文件:
#include<sys/types.h> #include<sys/stat.h>
int mkfifo(const char pathname,mode_t mode)*
pathname:FIFO文件名
mode:属性,一旦创建了一个FIFO就可以用open打开它,应用一般的文件访问函数(如:close、read、write等,都适用于FIFO的文件操作)。
-
没有使用O_NONBLOCK:访问要求无法满足时进程将阻塞,如试图读取一个空的FIFO,将导致进程阻塞。
-
使用O_NONBLOCK:访问要求无法满足时不会阻塞,立即出错返回,errno是ENXIO;
实例:fifo_write.c
#include<sys/types.h> #include<sys/stat.h> #include<errno.h> #include<fcntl.h> #include<stdio.h> #include<stdlib.h> #include<string.h> #define FIFO_SERVER "/tmp/mtfifo" void main() { int fd; char w_buf[100]; int nwrite; //打开管道 fd = open(FIFO_SERVER,O_WRONLY|O_NONBLOCK,0); if(argc == 1){ printf("Please send something \n"); exit(-1); } strcpy(w_buf,argv[1]); //向管道写入数据 if(nwrite = write(fd,w_buf,100)==-1){ if(errno == EAGAIN) printf("The FIFO has not been read yet,please try later.\n"); } else printf("write %s to the FIFO\n",w_buf); }
实例:fifo_read.c
#include<sys/types.h> #include<sys/stat.h> #include<errno.h> #include<fcntl.h> #include<stdio.h> #include<stdlib.h> #include<string.h> #define FIFO "/tmp/mtfifo" void main(int argc,char** argv) { char buf_r[100]; int fd; int nread; //creat if(mkfifo(FIFO,O_CREAT|O_EXEL)<0&&(errno != EEXIST)) printf("csnnot creat fifoserver\n"); printf("preparing for reading btes...\n"); memset(buf_r,0,sizeof(buf_r)); //打开管道 fd = open(FIFO,O_RDONLY|O_NONBLOCK,0); if(fd == -1){ perror("open"); exit(1); } while(1){ memset(buf_r,0,sizeof(buf_r)); if((nread=read(fd,buf_r,100))==-1){ if(errno == EAGAIN) printf("no data yet!\n"); } printf("read %s from FiFO \n",buf_r); sleep(1); } pause();//暂停,等待信号。 }
-
-
信号通信 kill 和 raise
发送信号:kill 和 raise
相关的头文件:
#include<sys/types.h> #include<signal.h>
kill既可以向自身发送信号,也可以向其他进程发送信号,与kill的函数不同的是,raise函数是向进程自身发送信号的。
int kill(pid_t pid,int signo)
int raise(int signo)
-
pid>0
将信号发送给进程ID为pid的进程
-
pid == 0
将信号发送给同组的进程
-
pid < 0
将信号发送给其进程组ID等于pid的绝对值的进程
-
pid == -1
将信号发送给所有的进程。
-
-
信号通信 Alarm 和 pause 函数
相关的头文件:
#include<unistd.h>
unsigned int alarm(unsigned int seconds)
经历过了指定的seconds秒后产生信号SIGALRM。
int pause(void)
pause函数使调用进程挂起直到捕捉到一个信号。只有执行了一个信号处理函数后,挂起才结束。
====》
#include<signal.h>
void(*signal (int signo,void (*func)(int)))(int)
typedef void(*sighandler)(int)sighandler_t signal(int signum,sighandler_t handler)
Func可能的值是:
SIG_IGN:忽略此信号。
SIG_DFL:按照系统的默认处理方式处理。
信号处理函数名:使用该函数进行处理。
例子:mysig.c
#include<signal.h>
#include<stdio.h>
#include<stdlib.h>
void my_func(int sign_no)//一个参数,用来传送信号。
{
if(sign_no == SIGINT)
printf("I have get SIGINT\n");
else if(sign_no == SIGQUIT)
printf("I have get signal SIGQUIT \n");
}
int main()
{
printf("Waiting for signal SIGINT or SIGQUIT \n");
//注册信号处理函数//
signal(SIGINT,my_func);//用signal来为用户函数注册
signal(SIGQUIT,my_func);
pause();//进程在这里等待,直到完成一个函数。
exit(0);
}
-
共享内存通信方式
共享内存是在进程间共享数据的一种最快的方法,一个进程向共享内存区域写入数据,共享这个内存区域的所有进程就可以立即看到其中的内容。
使用步骤:
1. 创建共享内存,使用**shmget**函数 2. 映射共享内存,将这段创建的共享内存映射到具体的进程空间去,使用**shmat**函数
创建:
int shmget(key_t key,int size,int shmflg)
key标识共享内存的键值:0/IPC_PRIVATE。当key为IPC_PRIVATE,则函数shmget()将创建一块新的共享内存;如果key的值为0,而参数shmflg中又设置IPC_PRVATE这个标志,则同样会创建新的共享内存。
返回值:成功==》返回共享内存标识符, 失败==》返回-1.
映射:
int shmat(int shmid,char* shmaddr,int flag)
shmid: shmget函数返回的共享存储标识符。
flag:决定以什么样的方式来确定映射的地址(通常为0)。
返回值:成功==》返回共享内存映射到进程中的地址,失败==》放回值-1
解除映射:
int shmdt(char* shmaddr)
当一个进程不再需要共享内存时,需要把它从进程地址空间中脱离出来。即解除映射。
例子:shmem.c
#include<stdlib.h> #include<stdio.h> #include<string.h> #include<errno.h> #include<unistd.h> #include<sys/stat.h> #include<sys/types.h> #include<sys/ipc.h> #include<sys/shm.h> #define PERM S_IRUSR|S_IWUSR // 共享内存 // int main(int argc,char **argv) { int shmid; char *p_addr,*c_addr; if(argc!=2){ fprintf(stderr,"Usage:%s\n\a",argv[0]); exit(1); } //创建共享内存// if((shmid = shmget(IPC_PRIVATE,1024,PERM))==-1){ fprintf(stderr,"Create share memory error: %s\n\a",strerror(errno)); exit(1); } //创建子进程/// if(fork()){//父进程写 p_addr = shmat(shmid,0,0);// 内存地址映射 // memset(p_addr,'\0',1024);// 对某一个内存区进行格式化 strncpy(p_addr,argv[1],1024);//拷贝argv中的内容到共享内存中去 wait(NULL);//释放资源,不关心终止状态(等待一个子进程完成) exit(0); } else{//子进程读 sleep(1); c_addr = shmat(shmid,0,0);// 内存地址映射 // printf("Client get %s\n",c_addr); exit(0); } }
— 进程间通信(二)
-
消息队列
相关头文件 #include<sys/types.h> #include<sys/ipc.h> #include<sys/msg.h>
键值:
key_t ftok(char* pathname,char proj)
pathname: 文件名
proj: 项目名(不为0即可)。
功能:返回文件名对应的键值。
打开/创建:
int msgget(key_t key,int msgflg)
key: 键值,由ftok获取到。
proj: 标志位。
功能:返回键值key相对应的消息队列描述字。
标志位:
IPC_CREAT :创建新的消息队列
IPC_EXCL :与IPC_CREAT一同使用,表示如果要创建的队列已经在了,则返回错误。
IPC_NOWAIT :读写消息队列要求无法得到满足时,不阻塞。
实例:msgget.c
int open_queue(key_t keyval) { int qid; if((qid=msgget(keyval,IPC_CREAT))== -1 ) { return (-1); } return(qid); }
发送消息:
int msgsnd(int msqid,struct msgbuf* msgp,int msgsz,int msgflg)
msqid: 已经打开的消息队列id
msgp: 存放消息的结构
msgsz:消息数据长度
msgflg:发送标志,有意义的msgflg标志为IPC_NOWAIT,指明在消息队列没有足够空间容纳要发送的消息时,msgsnd是否等待。
功能:向消息队列中发送一条消息。
消息格式:
struct msgbuf
{
long mtype; // 消息类型 大于 0 //
char mtext[1]; // 消息数据的首地址 //
}
接收消息:
int msgrcv(int msqid,struct msgbuf* msgp,int msgsz,long msgtyp,int msgflg)
msqid: 已经打开的消息队列id
msgp: 存放消息的结构
msgsz:消息数据长度
msgflg:发送标志,有意义的msgflg标志为IPC_NOWAIT,指明在消息队列没有足够空间容纳要发送的消息时,msgsnd是否等待。
功能:从msgqid代表的消息队列中读取一个msgtyp类型的信息,并且把消息存存储在msgp指向的msgsbuf结构中。在成功读取了一条信息后,队列中的这条信息将被删除。
例子:
int read_message(int qid,long type,struct mymsgbuf* qbuf) { int result,length; length = sizeof(struct mymsgbuf)-sizeof(long); if((result = msgrcv(qid,qbuf,length,type,0)) == -1) return(-1); return(result); }
测试例子:msg.c
#include<sys/types.h> #include<sys/msg.h> #include<unistd.h> struct msg_buf{ int mtype; char data[255]; }; int main(){ key_t key; int msgid; int ret; struct msg_buf msgbuf; key = ftok("/tmp/2",'a'); printf("key = [%x]\n",key); msgid = msgget(key,IPC_CREAT|0666); if(msgid == -1) { printf("creat error !\n"); return -1; } msgbuf.mtype = getpid();//进程号!!! strcpy(msgbuf.data,"test haha"); ret = msgsnd(msgid,&msgbuf,sizeof(msgbuf.data),IPC_NOWAIT);//最后一个是标志位,标识不阻塞! if(ret == -1){ printf("send message err\n"); return -1; } memset(&msgbuf,0,sizeof(msgbuf)); ret = msgrcv(msgid,&msgbuf,sizeof(msgbuf.data),getpid(),IPC_NOWAIT); if(ret == -1) { printf("recv message err\n"); return -1; } printf("recv message = [%x]\n",msgbuf.data); }
-
信号量
相关头文件 #include<sys/types.h> #include<sys/ipc.h> #include<sys/sem.h>
键值:
key_t ftok(char* pathname,char proj)
pathname: 文件名
proj: 项目名(不为0即可)。
功能:返回文件名对应的键值。
打开/创建:
int semget(key_t key,int nsems,int semflg)
key: 键值,由ftok获取到。
nsems: 指定打开或者新创建的信号灯集合中将包含信号灯的数目
semflg:标识,同消息队列
功能:返回键值key相对应的消息队列描述字。
操作:
int semop(int semid,struct sembuf*sops,unsigned nsops)
semid: 信号量集合的id
sops: 是一个操作数组,表明要进行什么操作
nsops:nsops所指向的数组的元素个数
功能:对信号量进行控制。
struct sembuf{ unsigned short sem_num;//要操作的信号量在集中的编码,第一个编码为0; short sem_op;//释放或获取 short sem_flg; }