1、linux进程概述
进程是程序执行的实例,它是linux的基本调度单位。一个进程由如下元素组成:
⨀ 程序当前的上下文,即程序的当前执行状态;
⨀ 程序的当前执行目录
⨀ 程序访问的文件和目录
⨀ 程序的访问权限,比如他的文件模式和所有权
⨀ 内存和其他分配给进程的系统的资源
⨀ 主调和被调之间叫做父子进程,父子进程内的数据往往有一定的访问能力。
linux内核使用进程来控制对CPU和其他系统资源的访问,并且使用进程来决定多久以及采用什么特性运行它。内核的调度器负责在所有的进程间分配CPU执行时间,称为时间片(time slice),它轮流在每个进程分得的时间片用完之后从进程那里抢回控制权。
1.1 进程标识
OS会为每个进程分配一个唯一的整形ID,作为进程的标识号(pid)。进程处理自身的ID之外,还有父进程的ID(ppid),所有进程的祖先进程是同一个进程,他叫做init进程,ID为1,init进程是内核自举后启动的一个进程。init进程负责引导系统,启动守护(后台)进程并且运行必要的组件。
进程的pid和ppid可以分别通过函数getpid()和函数getppid()来获得。
/*测试子进程和父进程的ID*/ #include<stdio.h> #include<stdlib.h> #include<unistd.h> int main() { printf("PID: %d ppid: %d \n", getpid(), getppid()); return 0; }
每次使用Putty连接ubuntu虚拟机之后,都会产生一个新的bash(服务端),在其中开启的子进程是客户端。
1.2 进程用户ID与组ID(进程运行身份)
进程在运行过程之中,必须具有一个类似于用户的身份,以便于进程的权限控制,缺省情况下,哪个登录用户运行程序,该程序进程就具有该用户的身份。例如,假设当前登录用户为gotter,它运行了ls程序,则ls在运行过程中就具有gotter的身份,该ls进程的用户ID和组ID分别为gotter和gotter所属的组。这类型的ID叫做进程的真实用户ID和真实组ID。
真实用户ID和真实组ID可以通过函数getuid()和geigid()来获得。
/*测试用户ID和其对应组的ID*/ #include<stdio.h> #include<stdlib.h> #include<unistd.h> int main() { printf("UID: %d gid: %d euid: %d egid: %d\n", getuid(), getgid(), geteuid(), getegid()); return 0; }
2、Linux进程的创建
Linux下有四类创建子进程的函数:system(), fork(), exec*(), popen()
2.1 system
函数
原型:
#include<stdlib.h> int system(const char *string);
system函数通过调用shell程序**/bin/sh -c来执行string所指定的命令,该函数在内部是通过调用execve(/bin/sh,..)函数来实现的。通过system创建子进程之后,原进程和子进程各自运行,相互之间关联较少。如果system调用成功,将返回0。system函数同windows下一样,是同步阻塞**的函数。
2.2 fork
函数
fork族函数包括fork
和vfork
两个函数。
原型:
#include<unistd.h> pid_t fork(void);
返回值:<0 表示错误; ==0表示子进程在此条件下写代码; >0表示父进程可以在此写代码。
fork函数的创建子进程的过程为:在进程调用fork时,系统根据现有的进程的特性几乎是完全意义地复制出一个新的子进程,复制的内容包括原进程的当前内存空间的代码、变量、对象等所有的内存状态,真实和有效uid和gid,环境,资源限制,打开的文件等。通过这种复制方式 创建出子进程之后,原有进程的子进程都从函数fork返回,各自继续往下运行,但是原进程的fork返回值与子进程的fork返回值不同,在原进程中,fork返回子进程的pid,而在子进程中,fork返回0,如果fork返回负值,表示创建子进程失败。
#include<stdlib.h> #include<unistd.h> #include<stdio.h> int main() { printf("Parent process id: %d\n",getpid()); pid_t iRet = fork(); if(iRet < 0) { printf("Create child process failed!\n"); return -1; } else if(iRet == 0) {//子进程 printf("I'm a child process, and id: %d ppid: %d\n", getpid(), getppid()); getchar(); } else {//回到主进程 printf("Create child process succeed, child id: %d\n", iRet); sleep(1); getchar(); } return 0; }
测试Linux多进程(process)的原理:
#include<iostream> #include <pthread.h> #include <unistd.h> using namespace std; void* theProc(int n) { while(true) { cout << n << endl; sleep(1); } } int main() { int n = 1; while(n>0) { cin >> n; pid_t iRet = fork(); if(iRet<0)//进程创建失败 break; if(iRet) //创建子进程成功 theProc(n); } return 0; }
测试Linux多进程(process)的全局变量是否为各个进程公用的,答案是否,各个进程会复制一份全局变量到自己的进程空间之中,所以Linux多进程不能使用全局变量的形式来进行进程间的相互通讯。
#include<iostream> #include <pthread.h> #include <unistd.h> using namespace std; int g_test = 0; void* theProc(int n) { int i = 0; while(i<60) { cout << g_test << endl; sleep(1); ++i; } } int main() { g_test = 1; while(g_test>0) { cin >> g_test; pid_t iRet = fork(); if(iRet<0)//进程创建失败 break; if(iRet) //创建子进程成功 theProc(g_test); } return 0; }
2.3 exec
函数族
exec*
是由一组函数组成,原型为:
#include <unistd.h> extern char **environ; int execl(const char *pathname, const char *arg, .../* (char *) NULL */); int execlp(const char *file, const char *arg, .../* (char *) NULL */); int execle(const char *pathname, const char *arg, .../*, (char *) NULL, char *const envp[] */); int execv(const char *pathname, char *const argv[]); int execvp(const char *file, char *const argv[]); int execvpe(const char *file, char *const argv[],char *const envp[]);
exec函数族的工作过程与fork完全不同,fork是在赋值一份原进程,而exec函数是用exec的第一个参数指定的程序在现有的进程空间中运行,并且用新进程完全覆盖现有的进程空间。
path是包括可执行文件名的全路径名,而不是可执行文件所在的路径;
file既可以是全路径名,也可以是可执行文件名;
arg是可执行文件的命令行参数,可以有许多个,但通常的一个参数应该是可执行文件的全路径名,注意最后一个参数必须为NULL;
argv是产地参数的另外一种形式,他是一个字符串数组,一般形式为:char *argv[] = {"full path","param1","param2",...,NULL}
envp也是一个字符串数组,他指定新进程的环境变量,一般形式为:char *envp[]={"name=val1","name2=val2",...,NULL};
对于参数envp的函数调用,新进程中的全局变量environ指针指向的环境变量数组将会被envp中的内容替代。
//测试1#include<stdlib.h>#include<unistd.h>#include<stdio.h>int main(int argc, char* argv[]){ for(int i = 0; i < argc; ++i) printf("arg%d:%s\n",i,argv[i]); char** ppEnv = environ; while(ppEnv && *ppEnv) printf("%s\n",*ppEnv++); return 0;}
//测试2#include<stdlib.h>#include<unistd.h>#include<stdio.h>int main(){ char *arrArg[] = {"exec1.exe", "abc", "567","bbb",NULL}; char *arrEnv[] = {"env1=111","env2=222",NULL}; int iRet = 0; //iRet = system("ls -l"); //iRet = execl("ls","-l", NULL); //iRet = execl("/bin/ls", "ls","-l", NULL); //iRet = execl("./exec1.exe", "exec1.exe","abc", NULL); //iRet = execlp("./exec1.exe", "exec1.exe","abc", NULL); //iRet = execlp("ls","ls","-l",NULL); //iRet = execv("./exec1.exe",arrArg); iRet = execve("./exec1.exe",arrArg, arrEnv); return 0;}
2.4 popen
函数
popen
是一种类似于system
的函数,它是一种无需fork
和exec
就能执行外部程序的简易方法。与system
不同之处在于它使用管道工作。原型为:
#include<stdio.h>FILE *popen(const char *command, const char *type);int pclose(FILE *stream);
command为可执行文件的全路径和执行参数;
type可选参数为“r”或者“w”,如果为“w”,则popen返回的文件流作为新进程的标准输入流,即stdin,如果为"r",则popen返回的文件流作为新进程的标准输出流。
pclose等待新进程的结束,而不是杀死新进程
//子程序,被调程序#include<stdlib.h>#include<unistd.h>#include<stdio.h>int main(){ char szBuf[32]; fgets(szBuf, sizeof(szBuf),stdin); fputs(szBuf, stdout); sleep(3); return 0;}
//主程序,主调程序#include<stdlib.h>#include<unistd.h>#include<stdio.h>int main(){ FILE *pf = popen("./popen1.exe", "w"); if(!pf) { puts("popen error!"); return -1; } char s[32]; puts("Please input some text: "); gets(s); fputs(s,pf); pclose(pf); return 0;}
3、进程的控制与终止
3.1 fork
进程终止
一个进程创建子进程后,如果子进程先退出,系统呢不会自动清理掉子进程的环境,而必须由父进程调用wait或者waitpid函数来完成清理工作,如果父进程不做清理工作,则已经退出的子进程将成为僵尸进程(defunct),在系统中如果存在的僵尸进程过多,将会影响系统的性能,所以必须对僵尸进程进行处理。
另一种情况,如果父进程先于子进程退出,则子进程成为孤儿进程,孤儿进程退出后,他的清理工作由祖先进程init自动处理。wait或者waitpid函数的函数原型如下所示:
#include<sys/types.h>#include<sys/wait.h>pid_t wait(int *status);pid_t waitpid(pid_t pid, int *status, int options);
wait和waitpid都等待一个已经退出的子进程,并进行清理工作;
wait函数随机等待一个已经退出的子进程,并返回该子进程的pid;
waitpid等待指定pid的子进程;
status参数是传出参数,存放子进程的退出代码;
option的可选项有2个,WNOHANG和WUNTRACED,两个参数可以用 | 合成。WNOHANG 表示无论子进程是否退出都将立即返回, WUNTRACED极少使用。
#include<stdlib.h>#include<unistd.h>#include<stdio.h>#include<sys/types.h>#include<sys/wait.h>#include<signal.h>//回调函数void SignChildPsExit(int iSignNo){ pid_t pid; int iExitCode; printf("your son is exit %d\n", getpid()); pid = wait(&iExitCode); printf("signNo:%d child %d exit\n",iSignNo,pid);}int main(){ // signal(SIGCHLD, SignChildPsExit); // printf("Parent process id:%d\n",getpid()); pid_t iRet = fork(); if(iRet<0) { printf("Create child process fail!\n"); return -1; } if(iRet) { printf("I am Parent %d.\n", getpid()); int iExitCode; pid_t pid = wait(&iExitCode); printf("%d’s child exit\n", pid); } else { printf("I am Child %d.\n", getpid()); sleep(3); } printf("Process %d exit.\n", getpid()); return 0;}
3.2 进程终止的5种方式
·main函数自然返回
·调用exit函数
·调用_exit函数
·调用abort函数
接收能导致进程终止的信号
前3种方式为正常的终止,后2中为非正常的终止。但是无论哪种方式,进程终止都将执行相同关闭打开的文件,释放占用的内存等资源。只是后两种终止会导师有些代买不会正常执行,比如对象的析构,atexit函数的执行等。
kill函数,其原型为:
#include<sys/types.h>#include<signal.h>int kill(pid_t pid, int sig);
kill
函数给进程pid发送信号sig,可发送的信号可以通过shell命令kill -l
查看,接收信号的进程如果不对信号进行处理,将会被自动终止。
4、进程间打开文件的继承
4.1 用fork
继承打开的文件
fork
以后的子进程自动继承了父进程的打开的文件,继承以后,父进程关闭掉打开的文件不会对子进程造成影响。
//示例#include<stdio.h>#include<fcntl.h>#include<unistd.h>#include<sys/types.h>#include<sys/stat.h>int main(){ char szBuf[256] = {0}; int iFile = open("./a.txt", O_RDONLY); if(fork()>0) {//关闭父进程 close(iFile); puts("The Parent process is exited"); return 0; } //child process sleep(3);//等待一段时间,保证父进程完全退出 if(read(iFile, szBuf, sizeof(szBuf)-1)<1) { perror("read fail!"); } else { printf("string:%s\n", szBuf); } close(iFile); return 0;}
4.2 用exec*
继承打开的文件
用exec*
创建的子进程照样可以继承已经打开的文件,示例:
//Parent.cpp #include<cstdio> #include<fcntl.h> #include<unistd.h> #include<sys/types.h> #include<sys/stat.h> int main() { char szBUf[32] = {0}; if(fork()>0) //parent process return 0; //child process close(STDOUT_FILENO); int iFile = open("./a.txt",O_WRONLY|O_CREAT,0666); if(iFile!=STDOUT_FILENO) { perror("open a.ext fail!"); return -1; } lseek(iFile, 0, SEEK_END);//到文件的尾部 execl("./c.exe","c.exe", NULL); perror("execl fail!"); return 0; }
#include<stido.h> #include<unistd.h> int main() { printf("hello\n"); return 0; }
5、 进程间通讯(interprocess communication,IPC)
5.1 用管道(FIFO)传递打开的文件的信息
如果用exec*创建子进程,虽然子进程可以自动继承父进程打开的文件,但是因为exec*之后,子进程完全替换掉 了父进程的空间,所以比较难获取打开的文件描述符,在这种情况下,必须采用进程间的通讯机制,让子进程知道自己继承了一些什么,已经打开了什么文件,etc。
建立一个管道:mkfifo("MyFifo",0666) create named pipes(FIFOS) with the given NAMES.:
另一进程打开管道:open("MyFifo",O_WRONLY)
//parent进程#include<cstdio> #include<fcntl.h> #include<unistd.h> #include<sys/types.h> #include<sys/stat.h> #include<string.h> int main(){ int fdFifo = open("MyFifo", O_WRONLY); if(fdFifo < 0) { perror("Create fifo fail!"); return -1; } char s[256]; do { puts("Please input some text: "); fgets(s,sizeof(s),stdin); //scanf("%s",s); write(fdFifo, s, strlen(s)); } while(*s!='#'); close(fdFifo); return 0; }
//child进程#include<cstdio>#include<fcntl.h>#include<unistd.h>#include<sys/types.h>#include<sys/stat.h>#include<string.h>int main(){ int fdFifo; //, fdFile; fdFifo = open("MyFifo", O_RDONLY); if(fdFifo<0) { perror("child open fifo fail!"); return -1; } char s[1024]; int n = 0; //int n = read(fdFifo, s, sizeof(s); while((n = read(fdFifo, s, sizeof(s))) > 0) { s[n] = 0; printf("%s",s); if(*s=='#') break; } close(fdFifo); return 0;}/*if(n < 1){ perror("read fifo fail!"); return -2;}*/
fifo
的函数创建:
#include <sys/types.h>#include<sys/stat.h>#include<unistd.h>#include<stdio.h>int main(int argc, char *argv[]){ mode_t iRight = 0666; if(argc != 2) { puts("Usage: Mkfifo.exe {filename}"); return -1; } if(mkfifo(argv[1], iRight) < 0) { perror("mkfifo fail!"); return -2; } return 0;}
删除文件的原型函数为:unlink(const char *pathname);
fifo
循环发送的例子:
//parent程序#include<time.h>#include<stdlib.h>#include<string.h>#include<sys/types.h>#include<sys/stat.h>#include<unistd.h>#include<stdio.h>#include<fcntl.h>int main(){ char szBuf[128]; int iRet, fdFifo; puts("open for writing."); fdFifo=open("MyFifo.pip", O_WRONLY); if(fdFifo<0) { perror("open fifo fail!"); return -1; } puts("begin write\n"); srand(time(NULL)); for(int i = 0; i < 10; ++i) { sprintf(szBuf, "ps %d write %d", getpid(), i); printf("%s\n", szBuf); iRet = write(fdFifo, szBuf, strlen(szBuf)); if(iRet < 0) { perror("write fifo fail"); return -2; } sleep(1); } return 0;}
//child程序#include<time.h>#include<stdlib.h>#include<string.h>#include<sys/types.h>#include<sys/stat.h>#include<unistd.h>#include<stdio.h>#include<fcntl.h>int main(int argc, char *argv[]){ char szBuf[128]; int iRet, fdFifo; puts("open for reading."); fdFifo = open("MyFifo.pip", O_RDONLY); if(fdFifo<0) { perror("open fifo fail"); return -1; } puts("begin read\n"); while(true) { sleep(1); iRet = read(fdFifo, szBuf,sizeof(szBuf)); if(iRet < 1) break; else { szBuf[iRet] = 0; printf("read:%s\n",szBuf); } } close(fdFifo); return 0;}
fifo
发送文件句柄的例子:
//parent文件#include<time.h>#include<stdlib.h>#include<string.h>#include<sys/types.h>#include<sys/stat.h>#include<unistd.h>#include<stdio.h>#include<fcntl.h>#include<errno.h>int main(){ char szBuf[128]; int iRet, fdFifo, fdFile = open("./a.txt", O_WRONLY|O_CREAT, 0666); if(fdFile<0) { perror("open a.txt fail"); return -1; } lseek(fdFile, 0, SEEK_END); if(mkfifo("MyFifo", 0666) < 0 && errno != EEXIST) { perror("create fifo fail"); return -2; } if(fork() >0) {//parent process close(fdFile); if((fdFifo=open("MyFifo", O_WRONLY))<0) { perror("open fifo fail"); return -3; } write(fdFifo, &fdFile, sizeof(fdFile)); close(fdFifo); } else {//child process execl("./c.exe", "c.exe", NULL); perror("execl fail!"); } return 0;}
//child process#include<time.h>#include<stdlib.h>#include<string.h>#include<sys/types.h>#include<sys/stat.h>#include<unistd.h>#include<stdio.h>#include<fcntl.h>#include<errno.h>#include<string.h>int main(){ int fdFile, fdFifo; char szBuf[] = "written by child.\n"; fdFifo = open("MyFifo", O_RDONLY); if(fdFifo<0) { perror("read fifo fail"); return -2; } if(read(fdFifo, &fdFile, sizeof(fdFile)) <1) { perror("read fifo fail"); return -2; } close(fdFifo); write(fdFile, szBuf, strlen(szBuf)); puts("I have written some text in a.txt!"); close(fdFile); return 0;}
5.2 无名管道(PIPE)
pipe可以看做一个形似管子的文件,管子的一端写数据进去,另外一端读数据出来。无名管道通常用于有继承关系的两进程间的传递数据,应为pipe是半工的,所以只能是一个进程传递数据给另外一个进程。
管道函数的原型:
#include<unistd.h>int pipe(int fds[2]);
管道程序中用一对文件描述符表示,其中一个文件描述符有可读属性,一个有可写属性。函数pipe用于创建一个无名管道,如果成功,fds[0]存放可写的文件描述符,fds[1]存放可写的文件描述符,并且返回0,否则返回-1。
通过调用pipe获取这对打开的文件描述符后,一个进程就可以往fds[0]中写数据,而另一个进程就可以从fds[1]中读数据出来。当然两个进程之间必须有继承关系,才能继承这对打开的文件描述符。管道不想真正的物理文件,不是持久的,即两个进程终止之后,管道也自动消失了。
用pipe实际是系统内部指定了fifo,不用自己创建fifo。
//test pipe handle#include<stdio.h>#include<string.h>#include<unistd.h>int main(){ int fds[2];//准备两个表示接收和发送的两个句柄 int iRet; char szBuf[32]; printf("fds[0]:%d fds[1]:%d\n",fds[0],fds[1]); pipe(fds); printf("fds[0]:%d fds[1]:%d\n",fds[0],fds[1]); return 0;}
//parent#include<stdio.h>#include<string.h>#include<unistd.h>int main(){ int fds[2];//准备两个表示接收和发送的两个句柄 int iRet; char szBuf[32]; printf("I am parent %d \n", getpid()); pipe(fds); printf("fds[0]:%d fds[1]:%d\n",fds[0],fds[1]); if(fork()==0) {//父进程 close(fds[1]); iRet=read(fds[0],szBuf,sizeof(szBuf)); if(iRet>0) { szBuf[iRet]=0; printf("I am parent %d read:%s\n", getpid(), szBuf); } else printf("read ret:%d\n", iRet); close(fds[0]); } else {//子进程 close(fds[0]); strcpy(szBuf, "Hello, child ps."); iRet = write(fds[1], szBuf, strlen(szBuf)); printf("I am child %d wrote:%s\n",getpid(),szBuf); close(fds[1]); } return 0;}
管道两端的关闭是有先后顺序的,如果先关闭写端,则从另外一端读取数据时,read函数将返回0,表示管道已经关闭。
但是如果先关闭读端,则往另外一端写数据时,将会使写数据的进程接收到SIGPIPE信号,如果写进程不对该信号进行处理,将导致写进程终止;如果写进程处理了该信号,则写数据的write函数将返回一个负值,表示管道已经关闭。
#include<signal.h>#include<stdio.h>#include<string.h>#include<unistd.h>int main(){ int fds[2]; int iRet; char szBuf[32]; //注释掉这部分将导致写进程被信号SIGPIPE终止,有这一部分后续程序会输出error信息,否则 不会输出error信息。 /* sigset_t setSig; sigempty(&setSig); sigaddset(&setSig,SIGPIPE); sigprocmask(SIG_BLOCK, &setSig, NULL); */ pipe(fds); if(fork()==0) {//子进程 close(fds[0]); close(fds[1]); } else {//父进程 close(fds[0]); sleep(2); strcpy(szBuf, "Hello, child ps."); iRet = write(fds[1], szBuf, strlen(szBuf)); if(iRet<0) printf("wrote ret %d\n",iRet); else printf("ps %d wrote:%s\n",getpid(),szBuf); close(fds[1]); } return 0;}
5.3 共享内存
共享内存也是进程间(进程间不需要有继承关系)交流信息的一种常用的手段。通常情况下,OS通过内存映射与页面交换技术,使进程的内存空间映射到不同的物理内存,这样能保证每一个进程运行的独立性,不至于受其他进程的影响。但是,可以通过共享内存的方式,使不同的进程的虚拟内存映射到同一块物理内存,这样,一个进程往这块物理内存更新数据,另外的进程可以立即看到这块物理内存的修改。
原型如下所示:
#include<sys/types.h>#include<sys/ipc.h>#include<sys/shm.h>key_t ftok(const char *pathname, int proj_id);int shmget(key_t key, int size, int shmflg);void *shmat(int shmid, const void *shmaddr, int shmflg);int shmdt(const void *shmaddr);int shmctl(int shmid, int cmd, struct shmid_ds *buf);
函数ftok用于创建一个关键字,可以用该关键字关联一个共享内存段,参数pathname为一个全路径文件名,并且改文件必须可以访问;参数proj_id通常传入一个非0字符;通过pathname和prog_id组合可以创建唯一的key;如果调用成功,返回一个关键字,否则返回-1。
函数shmget用于创建或者打开一个共享内存段,该内段段由函数的第一个参数标识。参数key时一个与共享内存段相关联的关键字,如果实现已经存在一个与指定关键字关联的共享内存段,则直接返回该内存段的标识。key的值既可以用ftok产生,也可以是ICE_PRIVATE,表示总是创建新的共享内存段;参数size指定共享内存段的大小,通常是内存页面大小的倍数;参数shmflg时一个掩码合成值,可以是访问权限值与(IPC_CREAT或者IPC_EXCL)的合成,IPC_CREAT表示如果不存在该内存段,则创建它;IPC_EXCL表示如果该内存段存在,则函数返回失败结果(-1)。如果调用成功,返回内存段标识,否则返回-1。
函数shmat将共享内存段映射到进程空间的某一个地址。参数shmid是共享内存段的表示;参数shmaddr和shmflg通常为0;如果调用成功,返回映射后的进程空间地址,否则返回 (char∗)−1。
函数shmdt用于将共享内存段与进程空间进行分离。参数shmaddr通常应该是shmget的成功返回值。
函数shmctl可以删除共享给内存段。参数shmid是共享内存段的标识;参数cmd是对共享内存段的操作方式,可以选为IPC_STAT(状态),IPC_SET(清),IPC_RMID(删除);参数buf是标识共享内存段的信息结构体数据;例如shmctl(kshareMem, IPC_RMID,NULL)标识删除掉共享内存段kHareMem。
//示例0#include<sys/types.h>#include<sys/ipc.h>#include<sys/shm.h>#include<stdio.h>#include<string.h>#include<unistd.h>bool IsStr(const char *pszBuf, int iMaxSize){ while(iMaxSize-- > 0 && *pszBuf++) ; return *(pszBuf-1)==0;}int main(int argc, char* argv[]){ const int BUF_SIZE = 4000; FILE *pStream; key_t kShareMem; int iShareMem; char *pszBuf; if(argc!=2 || strcmp(argv[1],"read")!= 0 && strcmp(argv[1], "write")!=0) { printf("Usage: shareMem <read|write>\n"); } pStream = fopen("./a.bin","ab");//append 追加模式 if(pStream) fclose(pStream); kShareMem=ftok("./a.bin","a"); if(kShareMem==-1) { perror("ftok fail."); return -2; } iShareMem=shmget(kShareMem,BUF_SIZE,0666|IPC_CREAT); if(iShareMem<0) { perror("shmget fail."); return -3; } pszBuf=(char*)shmat(iShareMem,0,0); if(pszBuf==(char*)-1) { perror("shmat fail."); return -4; } if(strcmp(argv[1],"write")==0) { while(fgets(pszBuf, 256, stdin)) ; } else { char szOld[256]={0}; while(true) { if(IsStr(pszBuf,256)!=0 && strcpy(szOld, pszBuf) != 0) { strcpy(szOld, pszBuf); printf("%s", szOld); } sleep(1); } } shmdt(pszBuf); return 0;}
//示例1 #include<sys/types.h> #include<sys/ipc.h> #include<sys/shm.h> #include<stdio.h> #include<string.h> #include<unistd.h> bool IsStr(const char *pszBuf, int iMaxSize) { while(iMaxSize-- > 0 && *pszBuf++) ; return *(pszBuf-1)==0; } int main(int argc, char* argv[]) { const int BUF_SIZE = 4000; FILE *pStream; key_t kShareMem; int iShareMem; char *pszBuf; if(argc!=2 || strcmp(argv[1],"read")!= 0 && strcmp(argv[1], "write")!=0) { printf("Usage: shareMem <read|write>\n"); } pStream = fopen("./a.bin","ab");//append 追加模式 if(pStream) fclose(pStream); kShareMem=ftok("./a.bin",'a'); if(kShareMem==-1) { perror("ftok fail."); return -2; } iShareMem=shmget(kShareMem,BUF_SIZE,0666|IPC_CREAT); if(iShareMem<0) { perror("shmget fail."); return -3; } pszBuf=(char*)shmat(iShareMem,0,0); if(pszBuf==(char*)-1) {0 perror("shmat fail."); return -4; } if(strcmp(argv[1],"write")==0) { int &n = (int&) *pszBuf; n = 1; while(n) { scanf("%d",&n); } } else { int &n = (int&) *pszBuf; n = 1; while(n) { printf("n = %d\n",n); sleep(1); } } shmdt(pszBuf); return 0; }
5.4 消息队列
消息队列与FIFO很相似,都是一个队列结构,都可以有多个进程往队列里面写信息,多个进程从队列中读取信息。但FIFO需要读、写的两端都事先打开,才能够开始信息传递工作。而消息队列可以事先往队列中写信息,需要时再打开读取信息。
5.4.1 初始化消息队列
与共享内存相似,可以用msgget创建或者打开一个消息队列。其原型为:
#include<sys/types.h>#include<sys/ipc.h>#include<sys/msg.h>int msgget(key_t key, int msgflg);
参数key是唯一标识一个消息队列的关键字,如果为IPC_PRIVATE(值为0),表示创建一个只由调用进程使用的消息队列;非0值的key表示创建一个可以被多个进程共享的消息队列;参数magflg知名队列的访问权限和创建标志,创建标志的可选值为IPC_CREAT和IPC_EXCL,如果单独指定IPC_CREAT,magget要么返回新创建的消息队列id,要么返回具有想同key值的消息队列id;如果IPC_CREAT和IPC_EXCL同时指明,则要么创建新的消息队列,要么当队列存在时,调用失败并返回-1。
5.4.2 发送和接受消息
发送和接收消息的函数原型为:
#include<sys/types.h>#include<sys/ipc.h>#include<sys/msg.h>int msgsnd(int msqid, struct msgbuf *msgp, size_t msgsz, int msgflag);ssize_t msgrcv(int msqid, struct msgbuf *msgp, size_t msgsz, long msgtyp, int msgflg);
参数msgid指明消息队列的ID;参数msgbuf时消息结构体:
struct msgbuf{ long mtype; char mtext[1];}
字段mtype是用户自己指定的消息类型你,该结构体第2个成员仅仅是一种说明性的结构,实际上用户可以使用任何类型的数据;参数gsz是消息体的大小,每个消息体最大不超过4k;参数msgflg可以为0或者IPC_NOWAIT,如果设置IPC_NOWAIT,则msgsnd和msgrcv都不会阻塞,次数如果队列满并调用msgsnd,或者队列空时调用magrcv,则返回错误。参数msgtyp有3中选项:msgtyp==0表示接收队列中的第1个消息;msgtyp>0表示接收队列中的第一个类型等于msgtyp的消息;msgtyp<0表示接收器类型小于或者等于msgtyp绝对值的第1个最低类型消息。
6、守护进程(Daemon)
守护进程(Daemon)是在后台运行不受终端控制的进程。比如各种网络服务器,如web服务器,ftp服务器等。
想要脱离所有终端的原因是守护进程可能是从终端启动,在这之后这个终端能用来执行其他任务,或者关闭掉终端仍不影响已经启动的守护进程。简单一点的方法是启动的时候再命令行末尾添加一个&符号,例如nohup mysqlserver.out&,也可以写代码将程序转化成守护进程。
变为守护进程可以按照如下几步进行:
1、在父进程重新执行fork,并让父进程退出,从而断开程序与终端的关联;
2、在子进程中调用setsid();使新进程称为新的进程组组长和新会话期的领导;
3、让根目录成为进程的工作目录,因为如果守护进程是从一个可以挂断的文件系统中启动的话,如果不修改工作目录,在守护进程运行时,改文件系统就不能被卸载;
4、修改umask为0,避免在守护进程中创建的文件收到原有父进程umask的影响;
5、尽可能地关闭不需要的文件描述符。