博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
Linux进程和线程
阅读量:3938 次
发布时间:2019-05-23

本文共 26561 字,大约阅读时间需要 88 分钟。

目录

 


进程

概念

  进程:动态概念(运行中程序)

  程序:静态(一些指令集合)程序文件(可以移动)
  软件:文档加程序
  进程:进程是系统分配资源的最小单位
  进程控制(进程状态)动态--生命周期  创建--调度--销毁

 进程创建fork--系统调用

  pid_t fork(void)

  返回值
   <0创建失败
   ==0现在所处的进程是子进程
   >0 现在所处的进程是父进程(子进程的id号)
  fork的两种用法
   (1)一个父进程希望复制自己,使父,子进程同时执行不同代码段。这在网络服务进程中是常见的--父进程等待客户端的服务请求。当这种请求到达时,父进程调用fork,使子进程处理此请求。父进程则等待下一个服务请求到达
   (2)一个进程执行一个不同的程序。这对shell是常见的情况。在这种情况下,子进程从fork返回后立即调用exec
 

每一次fork都会产生一个子进程,而wait(),相当于递归一样,等待最后一个子进程死后才去打印它上一个进程:

#include 
#include
#include
#include
#include
int main(int argc, char **argv){ pid_t x; int i; for(i=0; i<10; i++) { x = fork(); if(x > 0) break; if(x == 0) continue; } wait(NULL); printf("PID %d PPID %d\n", getpid(), getppid()); return 0;}

 vfork---先运行子进程,(子进程退出后)运行父进程

 注意:一定要在子进程里要用exit()来退出子进程,不然会进入无限的循环。

  fork()与vfork()的区别~~~:>>  

主要为两点:  

(1)执行次序:fork():对父子进程的调度室由调度器决定的;        

          vfork():是先调用子进程,等子进程的exit(1)被调用后,再调用父进程;  

(2)对数据段的影响:fork():父子进程不共享一段地址空间,修改子进程,父进程的内容并不会受影响。            

 vfork():在子进程调用exit之前,它在父进程的空间中运行,也就是说会更改父进程的数据段、 栈和堆。。即共享代码区和数据区,且地址和内容都是一样的。

 进程ID

  每一个进程都有一个非负数整型表示的唯一ID。

  getpid()--获取当前进程的id号
  getppid()--获取当前进程的父进程id
  getuid()--调用进程用户的实际ID
  geteuid()--调用进程的有效用户ID
  getgid()---调用进程实际组ID
  getegid()--调用进程的有效组ID

 僵尸进程

  父进程还运行子进程已经退出(但是父进程没有回收子进程资源)(编程时候必须避免)

  父进程回收子进程资源  在父进程中调用wait
  pid_t wait(int *status)

 孤儿进程

  父进程已经退出子进程还在运行(最后都会把init进程作为父进程)

  操作过程:在一个进程终止时,内核逐个检查所有活动进程,以判断它是否是正在终止的子进程,如果是,则将该进程的父进程ID更改为1(init的进程ID)

 进程退出

   正常退出(程序运行完毕)
  调用exit退出 (会做善后处理,关闭文件,清空缓存)
   void exit(int status);会关闭所有IO流,进行冲洗
  调用_exit退出 (直接退出) void _exit(int status);
  status:子进程的退出值
  无返回值

 等待子进程

  进程一旦调用了wait,就立即阻塞自己,由wait自动分析是否当前进程的某个子进程已经退出。如果让它找到了这样一个已经变成僵尸的子进程,wait就会收集这个子进程的信息,并把它彻底销毁后返回;如果没有找到这样一个子进程,wait就会一直阻塞在这里,直到有一个出现为止。

   pid_t wait(int *status);  pid_t waitpid(pid_t pid, int *status, int options);
          WNOHANG:非阻塞等待
          WCONTINUED:报告任意一个从暂停态出来且从未报告的子进程的状态
          WUNTRQCED:报告任意一个当前处于暂停态且从未报告的子进程的状态

  子进程的结束状态值会由参数status返回,而子进程的进程PID也会一起返回,如果不在意结束状态,则参数status可以设置为NULL。如果执行成功则返回进程PID,如果失败则返回-1

  waipid:如果想让父进程周期性检查某个特定的子进程是否已经终止,可以使用waitpid(child_pid,(int *)0,WNOHANG)

 exec函数族(接管一个进程的所有资源)

int execl(const char *path, const char *arg, ...);  后面变参必须以NULL结束(类似于main函数参数)	execl("/bin/ls", "ls","-a", "-l", NULL);   ls -a -l 必须把要运行的程序路径加到PATH环境变中		int  execlp(const char *file, const char *arg, ...);   	execl("ls", "ls","-a", "-l", NULL);   ls -a -l 		int execle(const char *path, const char *arg,..., char * const envp[]);	char *envp[] = {"MM=/home/gec", "/usr/local", NULL};	execle("/home/gec/myshare/main", "main", NULL, envp);	int execv(const char *path, char *const argv[]);  	char *argv[] = {"ls","-a", "-l", NULL};	execv("/bin/ls", argv);		int execvp(const char *file, char *const argv[]);	int execvpe(const char *file, char *const argv[],char *const envp[]);	system("/home/gec/main")---类似于函数调用

 exec函数族成功执行后,原有的程序代码都将被指定的文件或脚本覆盖,,后面的代码无法运行,也无法返回

 守护进程

  是指在后台运行且不与终端相连街的一种进程(也称之为精灵进程或后台进程)

 精灵进程示例(将一句话写到日记文件里)

  daemon.h

#ifndef __DAEMON_H#define __DAEMON_H#include 
#include
#include
#include
#include
int daemon_init(void);#endif

daemon.c

#include "daemon.h" int daemon_init(void){	pid_t pid;	int fd0, fd1, fd2, max_fd,i;		// 1	if((pid = fork()) < 0) 	{		perror("fork faild!");		exit(1);	}	else if(pid != 0)//让父进程能出	{		exit(0);	}	// 2	signal(SIGHUP, SIG_IGN);//忽略终端被关闭的信号	// 3	if(setsid() < 0) //创建一个新的会话	{		exit(1);	}	// 4	if((pid = fork()) < 0)	{		perror("fork faild!");		exit(1);	}	else if(pid != 0)		exit(0);	// 5	setpgrp(); //设置孙子进程的进程组,彻底脱离会话	// 6  关闭看有的文件描述符,默认是1024个,但没有那么多打开,那么close就没反应的	max_fd = sysconf(_SC_OPEN_MAX);//获取文件打开的最大个数	for (i = max_fd; i>=0; i--)	{		close(i);//关闭文件	}	// 7 ; 0:表示不会影响你设置的权限,当你是umask(0777)的话,你要设置的文件权限都会被减为0,没有权限的意思	umask(0);	// 8 修改守护进程的当前工作路径,让它挂载到跟目录不会被卸载	chdir("/");	//Initialize the log file. 打开日记文件	openlog("daemon_test", LOG_CONS | LOG_PID, LOG_DAEMON);	return 0;}

  main.c

#include 
#include "daemon.h"int main(void){ daemon_init(); //初始化一个精灵进程 while(1) { syslog(LOG_DAEMON, "I am a daemonAAA!"); //将这句话写到日记文件里 sleep(2); //2S写一次,但日记里只会出现一次,下面会告诉你重复了多少次 } return 0;}

进程间通信

进程间通信  管道, 信号, 消息队列,信号量,内存共享

 管道

      无名管道

         无名管道--只使用于亲属进程(父子进程)

          PIPE的特征:
                    1、没有名字,因此无法使用open() (只能在一个进程中被创建出来,通过继承方式将它的文件描述符传递给子进程,这也是                   PIPE只能用于亲缘进程的原因)
                   2、 只能用于亲缘进程间的通信(父子进程,兄弟进程,祖孙进程)
                   3、 半双工工作方式:读写端分开
                   4、 写入操作不具有原子性,因此只能用于一对一的简单通信情型
                   5、 不能使用lseek()来定位(因为它的数据不像普通文件那样按块的方式存放在如硬盘,Flash等块设备上)

      创建无名管道

       int pipe(int pipefd[2]);
      从pipefd[1]写入,从pipefd[0]读出
 代码:

#include 
#include
#include
#include
#include
int main(int argc, char **argv){ int fd[2];//用于存放PIPE的两个文件描述符 int ret = pipe(fd); if(ret < 0) { perror("create pipe failed \n"); return -1; } pid_t pid = fork(); if(pid < 0) { perror("create fork failed\n"); } if(pid == 0) { write(fd[1],"hello world",12); } if(pid > 0) { char buf[12] = {0}; read(fd[0],buf,12); printf("%s\n",buf); } close(fd[0]); close(fd[1]); return 0;}

    管道和exec函数

     pipe2.c
   

#include 
#include
#include
#include
#include
#include
#include
int main(int argc, char **argv){ int processed; int fd[2]; const char data[] = "123"; char buf[BUFSIZ+1]; memset(buf,'\0',sizeof(buf)); int ret = pipe(fd); if(ret < 0) { perror("create pipe failed\n"); return -1; } pid_t pid = fork(); if(pid < 0) { perror("create fork failed\n"); return -1; } if(pid == 0) { //把读取管道数据的文件描述符保存到一个缓存区中, //该缓存区的内容将构成pipe3程序的一个参数 sprintf(buf,"%d",fd[0]); //(char *)0的作用是终止被调用程序的参数列表 (void)execl("pipe3","pipe3",buf,(char *)0); exit(EXIT_FAILURE); } if(pid > 0) { processed = write(fd[1],data,strlen(data)); printf("%d --wrote %d bytes\n",getpid(),processed); } return 0;}

     pipe3.c

#include 
#include
#include
#include
#include
#include
int main(int argc, char **argv){ int processed; char buf[BUFSIZ+1]; int descriptor; memset(buf,'\0',sizeof(buf)); sscanf(argv[1],"%d",&descriptor); processed = read(descriptor,buf,BUFSIZ); printf("%d --read %d bytes: %s\n",getpid(),processed,buf); return 0;}

  有名管道

   有一个实实在在的管道文件-

   FIFO的特征
    1、有名字,存储于普通文件系统之中
    2、任何具有相应权限的进程都可以使用open()来获取FIFO的文件描述符(程序不能以O_RDWR模式打开FIFO文件进行读写操作)
    3、跟普通文件一样,使统一的read()/write()来读/写
    4、跟普通文件不同,不能使用lseek()来定位,原因同PIPE
    5、具有写入原子性,支持多写者同时进行写操作而数据不会被践踏
    6、 First in First out 最先被写入FIFO的数据,最先被读出
   创建管道 mkfifo 文件名

int mkfifo(const char *pathname, mode_t mode);

   管道的读写

    读操作read
        有写者===》有数据===》 正常读取
                   ===》无数据===》 阻塞等待
     无写者===》有数据===》 正常读取

                ===》无数据===》立即返回

       
    写操作write

     有读者===》缓存未满===》正常写入
                         缓存已满===》阻塞等待
       
     无读者===》缓存未满===》立即收到SIGPIPE
               ===》 缓存已满===》 立即收到SIGPIPE
     
             
       

   FIFO间通信代码
    fifoA.c

#include 
#include
#include
#include
#include
#include
#define FIFOPATH "/tmp/fifo"int main(int argc, char **argv){ int ret = mkfifo(FIFOPATH, 00777); if(ret < 0 && errno != EEXIST) { perror("create fail"); return -1; } // int fd = open(FIFOPATH, O_RDWR); if(fd < 0) { perror("open fifo fail"); return -1; } char buf[32] = {0}; while(1) { scanf("%s", buf); ret = write(fd, buf, strlen(buf)+1); if(ret <= 0) { perror("write fail"); break; } if(strcmp(buf, "quit") == 0) break; } close(fd); return 0;}

    fifoB.c

#include 
#include
#include
#include
#include
#include
#define FIFOPATH "/tmp/fifo"int main(int argc, char **argv){ int ret = mkfifo(FIFOPATH, 00777); if(ret < 0 && errno != EEXIST) { perror("create fail"); return -1; } // int fd = open(FIFOPATH, O_RDWR); if(fd < 0) { perror("open fifo fail"); return -1; } char buf[32] = {0}; while(1) { ret = read(fd, buf, 32); if(ret <= 0) { perror("write fail"); break; } if(strcmp(buf, "quit") == 0) break; printf("wwww%s\n", buf); } close(fd); return 0;}

  popen()和pclose()
    FILE *popen(const char *command, const char *type);
         popen函数允许一个程序将另一个程序作为新进程来启动,并可以传递数据给它或者通过它接收数据

   参数

     command:字符串是要运行的程序名和相应的参数
     type:
              “r”:被调用的程序输出就可以被调用程序使用,调用程序利用popen函数返回的FILE*文件流指针,就可以通过常用的stdio库函数        (如fread)来读取被调用程序的输出
             “w”:调用程序就可以用fwrite嗲用向被调用程序发送数据,而被调用程序就可以在自己的标准输入上读取这些数据,被调用的程             序 通常不会意识到自己正从另一个进程读取数据,它只是在标准输入流上读取数据,然后做出相应的操作
    int pclose(FILE *stream);

   读取外部程序的输出:

#include 
#include
#include
#include
#include
#include
#include
int main(int argc, char **argv){ FILE *read_fp; char buffer[BUFSIZ+1]; int chars_read; memset(buffer,'\0',sizeof(buffer)); read_fp = popen("ls -l","r"); if(read_fp != NULL) { chars_read = fread(buffer,sizeof(char),BUFSIZ,read_fp); if(chars_read > 0) { printf("output was:-\n%s\n",buffer); } pclose(read_fp); exit(EXIT_SUCCESS); } return 0;}

  将读出送往外部程序:

#include 
#include
#include
#include
#include
#include
#include
int main(int argc, char **argv){ FILE *write_fp; char buffer[BUFSIZ+1]; sprintf(buffer,"Once upon a time,threr was...\n"); write_fp = popen("od -c","w"); if(write_fp != NULL) { fwrite(buffer,sizeof(char),strlen(buffer),write_fp); pclose(write_fp); exit(EXIT_SUCCESS); } return 0;}

 管道的弊端

无法在管道中读取一个指定的数据,因为这些数据没有做任何标记,读者进程只能按次序地逐个读取

 信号

 发送和接受信号

       kill()和signal()
            发送信号kill:

    信号拦截signal
           1、 sighandler_t signal(int signum, sighandler_t handler);(拦截的信号, 拦截后要做的事情)
           2、 typedef void (*sighandler_t)(int);
   

(升级板)sigqueue()和sigaction()

 

     struct sigaction的结构体

struct sigaction {               void     (*sa_handler)(int);               void     (*sa_sigaction)(int, siginfo_t *, void *);               sigset_t   sa_mask;               int        sa_flags;               void     (*sa_restorer)(void);           };

代码:

void ouch(int sig){  printf("ouch %d",sig);}int main(int argc, char **argv){    struct sigaction act;    act.sa_handler = ouch;    sigemptyset(&act.sa_mask);    act.sa_flags = 0;    sigaction(SIGINT,&act,NULL);    while(1)    {      printf("hello world\n");      sleep(1);    }	return 0;}

    sigaction.c:

#include 
#include
#include
#include
#include
#include
void f(int sig, siginfo_t *info, void *arg){ printf("sig: %d\n", sig); // SIGINT(2) printf("int: %d\n", info->si_int); // a.sival_int printf("from %d\n", info->si_pid);}int main(void){ pid_t x = fork(); if(x > 0) // 父进程 { struct sigaction act; bzero(&act, sizeof(act)); act.sa_sigaction = f; act.sa_flags = SA_SIGINFO; // 我要用扩展版的响应函数 // 向内核申请,将来收到SIGINT信号的时候,按照act结构体中 // 描述的情况来处理。NULL表示不需要内核返回该信号原来的处理模式 sigaction(SIGINT, &act, NULL); // signal()的扩展版 pause(); } if(x == 0) // 子进程 { union sigval a; a.sival_int = 100; printf("child PID: %d\n", getpid()); sleep(1); // 给父进程发送信号SIGINT,顺便还发了a这个值 sigqueue(getppid(), SIGINT, a); // kill()的扩展版 }}

  信号响应

   1、缺省动作

p1:int main(int argc, char **argv){    printf("my PID %d\n",getpid());    pause();	return 0;}p2:int main(int argc, char **argv){      pid_t pid;      scanf("%d",&pid);      kill(pid,SIGINT);		return 0;}通过p2发送信号将p1杀死

   2、忽略

p1:int main(int argc, char **argv){    printf("my PID %d\n",getpid());    signal(SIGINT,SIG_IGN);    pause();	return 0;}p2:int main(int argc, char **argv){      pid_t pid;      scanf("%d",&pid);      kill(pid,SIGINT);		return 0;}p2向p1发送杀死信号,没有反应,因为p1已经忽略该信号 SIGKILL和SIGSTOP都无法忽略

3、捕捉

p1:void f(int sig){  printf("catch sig:%d\n",sig);}int main(int argc, char **argv){    printf("my PID %d\n",getpid());    signal(SIGINT,f);    pause();	return 0;}p2:int main(int argc, char **argv){      pid_t pid;      scanf("%d",&pid);      kill(pid,SIGINT);		return 0;}f()函数由内核调用,函数预先告诉内核,等接受到SIGINT信号时,调用f()函数

   4、阻塞

  代码

void catch(int sig){  fprintf(stderr,"%d catch SIGQUIT\n",getpid());}int main(int argc, char **argv){    signal(SIGQUIT,catch);    sigset_t sig;    sigemptyset(&sig);    sigaddset(&sig,SIGQUIT);    sigprocmask(SIG_BLOCK,&sig,NULL);    pid_t pid;    pid = fork();    if(pid == 0)    {       fprintf(stderr,"child %d\n",getpid());       int i = 10;       while(i>0)       {               printf("%d\n",i--);        sleep(1);       }         sigprocmask(SIG_UNBLOCK,&sig,NULL);        while(1)	pause();     }    if(pid > 0)    {         fprintf(stderr,"parent %d\n",getpid());          sigprocmask(SIG_UNBLOCK,&sig,NULL);           while(1)	   pause();    }	return 0;}

因为SIGQUIT被阻塞了,子进程接收到SIGQUIT是没到反应,等到10秒后解除阻塞才有反应,而父进程一开始就已经解除阻塞

 其他

 查看或删除系统中的IPC对象

 获取一个当前未用的IPC的key

 消息队列MSG

  什么是消息队列

      消息队列提供一种带有数据标识的特殊管道,使用一段被写入的数据都变成带标识的消息,读取该段消息的进程只要指定这个标识就可以正确的读取,而不受到其他消息的干扰

消息队列使用方法

(1)发送者

  a、 获取消息队列的ID int msgget(key_t key, int msgflg);

   b、 将数据放入一个附带有标识的特殊的结构体,发送给消息队列

   
   (2)接收者
      A、 获取消息队列的ID

  
    B、将指定标识的消息读出

 
  (3  )设置或获取消息队列的相关属性
              1、int msgctl(int msqid, int cmd, struct msqid_ds *buf);
              2、msqid:消息队列ID
              3、cmd:
                   IPC_STAT:获取该MSG的信息,存储在结构体msqid_ds中
                   IPC_SET:设置该MSG的信息,储存在结构体msqid_ds中
                   IPC_RMID:立即删除该MSG,并唤醒所有阻塞在该MSG上的进程,同时忽略第三个参数
                   IPC_INFO:获取关于当前系统中MSG的限制值信息
                   MSG_INFO:获取关于当前系统中MSG的相关资源消耗信息
                   MSG_STAT:同IPC_STAT,但msgid为该消息队列的内核中记录所有信息的数组的下标,因此通过迭代所有的下标可以获得系统                中所有消息队列的相关信息
              4、buf:相关信息结构提的缓冲区
              5、IPC_STAT获取的属性信息被存放在一下结构体中

struct msqid_ds {              struct ipc_perm msg_perm;     /* Ownership and permissions */               time_t          msg_stime;    /* Time of last msgsnd(2) */               time_t          msg_rtime;    /* Time of last msgrcv(2) */               time_t          msg_ctime;    /* Time of last change */               unsigned long   __msg_cbytes; /* Current number of bytes in                                                queue (nonstandard) */               msgqnum_t       msg_qnum;     /* Current number of messages                                                in queue */               msglen_t        msg_qbytes;   /* Maximum number of bytes                                                allowed in queue */               pid_t           msg_lspid;    /* PID of last msgsnd(2) */               pid_t           msg_lrpid;    /* PID of last msgrcv(2) */           };

   6、其中,权限相关的信息用如下结构体来表示 

struct ipc_perm {               key_t          __key;       /* Key supplied to msgget(2) */               uid_t          uid;         /* Effective UID of owner */               gid_t          gid;         /* Effective GID of owner */               uid_t          cuid;        /* Effective UID of creator */               gid_t          cgid;        /* Effective GID of creator */               unsigned short mode;        /* Permissions */               unsigned short __seq;       /* Sequence number */           };

   (4)删除消息队列 msgctl(msgid,IPC_RMID,NULL);

  使用步骤

   1.设定一个key值

   2.在内核空间中申请队列(id)
   3.写数据
   4.读数据
   5.销毁

 共享内存段SHM

  什么是共享内存

     多个进程可以把一段内存映射到自己的进程空间,以此来实现数据的共享以及传输,这也是所有进程间通信方式中最快的一种

  共享内存的使用步骤

   1、获取共享内存对象的ID
           int shmget(key_t key, size_t size, int shmflg);
           key:共享内存的键值
           size:共享内存的尺寸(PAGE_SIZE的整数倍)
           shmflg:
                 IPC_CREAT:如果key对应的共享内存不存在,则创建
                 IPC_EXCL:如果该key对应的共享内存已存在,则报错
                 SHM_HUGETLB:使用大页面在分配共享内存
                 SHM_NORESERVE:不在交换分区中为这块共享内存保留空间
                 mode:共享内存的访问权限(八进制,如0644)
          返回值
              成功:该内存的ID
              失败:-1
        备注:如果key指定为IPC_PRVATE,则会自动产生一个随机未用的新键值
   2、将共享内存映射至本进程虚拟内存空间的某个区域
        void *shmat(int shmid, const void *shmaddr, int shmflg);
        int shmdt(const void *shmaddr);解除映射 shmaddr共享内存的首地址
        shmid:共享内存ID
        shmaddr:
              (1)如果为NULL,则系统会自动选择一个合适的虚拟内存空间地址去映射共享内存
              (2)如果不为NULL,则系统会根据shmaddr来选择一个合适的内存区域
        shmflg:
                SHM_RDONLY以只读的方式映射共享内存
                SHM_REMAP:重新映射,此时shmaddr不能为NULL
                SHM_RND:自动选择比shmaddr小的最大页对齐地址
                可用0,表示对共享内存可读可写
        第二参数被设置,那么第三个参数设置为SHM_RND,
        返回值
              成功:共享内存的首地址
              失败:-1
   3、当不在使用是,解除映射关系
   4、当没有进程再需要这块共享内存时,删除它
         int shmctl(int shmid, int cmd, struct shmid_ds *buf);
         shmid:共享内存ID
         cmd:
              IPC_STAT:获取属性信息,放到buf中
              IPC_SET:设置属性信息为buf指向的内容
              IPC_RMID:将共享内存标记为“立即被删除”状态
              IPC_INFO:获取关于共享内存的限制值信息
              SHM_INFO:获取系统为共享内存消耗的资源信息
              SHM_STAT:同IPC_STAT,但shmid为该SHM在内核中记录所有SHM信息的数组下标,因此通过迭代所有下标可以获得系统中所有           SHM的相关信息
              SHM_LOCK:禁止系统将该SHM交换至swap分区
              SHM_UNLOCK:允许系统将该SHM交换至swap分区
        返回值
             成功:
                IPC_INFO:内核中记录所有SHM信息的数组的下标最大值
                SHM_INFO:内核中记录所有SHM信息的数组的下标最大值
                SHM_STAT:x下标值为shmid的SHM的ID
            失败:-1
     IPC_STAT获得的属性信息被存放在以下结构体中:

struct shmid_ds {               struct ipc_perm shm_perm;    /* Ownership and permissions */               size_t          shm_segsz;   /* Size of segment (bytes) */               time_t          shm_atime;   /* Last attach time */               time_t          shm_dtime;   /* Last detach time */               time_t          shm_ctime;   /* Last change time */               pid_t           shm_cpid;    /* PID of creator */               pid_t           shm_lpid;    /* PID of last shmat(2)/shmdt(2) */               shmatt_t        shm_nattch;  /* No. of current attaches */               ...           };

 其中权限信息结构体:

struct ipc_perm {               key_t          __key;    /* Key supplied to shmget(2) */               uid_t          uid;      /* Effective UID of owner */               gid_t          gid;      /* Effective GID of owner */               uid_t          cuid;     /* Effective UID of creator */               gid_t          cgid;     /* Effective GID of creator */               unsigned short mode;     /* Permissions + SHM_DEST and                                           SHM_LOCKED flags */               unsigned short __seq;    /* Sequence number */           };

   5、可以通过shmctl获取或设置共享内存的相关属性
             int shmctl(int shmid, int cmd, struct shmid_ds *buf);
             shmid:共享内存的ID
             cmd:要采取的动作
                    常用的值
                    IPC_STAT:获取属性信息,放到buf中

struct shmid_ds {               struct ipc_perm shm_perm;    /* Ownership and permissions */               size_t          shm_segsz;   /* Size of segment (bytes) */               time_t          shm_atime;   /* Last attach time */               time_t          shm_dtime;   /* Last detach time */               time_t          shm_ctime;   /* Last change time */               pid_t           shm_cpid;    /* PID of creator */               pid_t           shm_lpid;    /* PID of last shmat(2)/shmdt(2) */               shmatt_t        shm_nattch;  /* No. of current attaches */               ...           };

         IPC_SET:设置属性为buf指向的内容
         IPC_RMID:删除共享内存段
         buf:属性信息结构体指针

 代码:
   shmA.c
 

#include 
#include
#include
#include
#include
#include
#include
#include
#include
int main(int argc, char **argv){ //1.获取key值 key_t key = ftok("/", 100); printf("key = %d\n", key); //2.获取共享内存id int shmid = shmget(key, 1024, IPC_CREAT|0777); if(shmid < 0) { perror("get id fail"); return -1; } //3.映射--链接 char *p = shmat(shmid, NULL, 0); memset(p, 0, 1024); memcpy(p, "hello haha", 11); getchar(); //4.释放映射 shmdt(p); //5.获取共内存状态 struct shmid_ds shmbuf; int ret = shmctl(shmid, IPC_STAT, &shmbuf); if(ret < 0) { perror("获取状态失败"); } //当映射该SHM的进程个数为0时,删除共享内存 if(shmbuf.shm_nattch == 0) { shmctl(shmid, IPC_RMID, NULL); } return 0;}

   shmB.c

#include 
#include
#include
#include
#include
#include
#include
#include
#include
int main(int argc, char **argv){ //1.获取key值 key_t key = ftok("/", 100); printf("key = %d\n", key); //2.获取共享内存id int shmid = shmget(key, 1024, IPC_CREAT|0777); if(shmid < 0) { perror("get id fail"); return -1; } //3.映射--链接 char *p = shmat(shmid, NULL, 0); if(p == NULL) { perror("shmat fail"); } printf("p=%s", p); getchar();}

 信号量SEM

1、概念

        信号量的P,V操作最核心的特征是:它们是原子性的,也就是说对信号量元素的值的增加和减少,系统保证CUP的电气特性级别上不可分割,这跟整型数据的加减法有本质的区别

2、 获取信号量的ID
    int semget(key_t key, int nsems, int semflg);
   key:信号量的键值
   nsems:信号量的个数
   semflg:
    IPC_CREAT:如果key对应的信号量不存在,则创建
    IPC_EXCL:如果key对应的信号量存在,则报错
    mode:信号量的访问权限
3、对信号量进行P/V操作,或等零操作
      int semop(int semid, struct sembuf *sops, unsigned nsops);
     semid:信号量的ID(是由semget返回的信号量标识符)
     sops:信号量操作结构体数组

struct sembuf{  unsigned short sem_num;  /* 信号量的元素序号(数组下标)(除非需要使用一组信号量,否则它的取值一般为0) */  short          sem_op;   /* 操作元素(通常只会用到两个值,一个是-1,也就是P操作,它等待信号量变为可用,一个是+1,也就是V操作,它发送信号表示信号量现在已可用) */  short          sem_flg;  /* 操作选项 */}

     nsops:结构体数组元素个数

 4、 获取或设置信号量的相关信息
   int semctl(int semid, int semnum, int cmd, ...);
   semid:信号量ID
   semmum:信号量元素序号(当需要用到成组的信号量是,就需要用到这个数,它一般取值为0,表示这是第一个也是唯一一个信号量)
   cmd:将要采取的动作
    两个常用的值
    SETVAL:用来把信号量初始化为一个已知的值,这是通过union semun中的val成员设置,其作用是咋信号量第一次使用之前对它进行设置

union semun {               int              val;    /* Value for SETVAL */               struct semid_ds *buf;    /* Buffer for IPC_STAT, IPC_SET */               unsigned short  *array;  /* Array for GETALL, SETALL */               struct seminfo  *__buf;  /* Buffer for IPC_INFO                                           (Linux-specific) */           };

    IPC_RMID:用于删除一个已经无需继续使用的信号量标识符

线程

1、概念

线程是系统调度的最小单位。

进程资源(进程间数据独立)  线程资源(一个进程中的多个子线程是共用进程资源)(数据段, 堆)线程有自己的独立栈空间

 2、创建一条新的线程

  int pthread_create(pthread_t *thread, const pthread_attr_t *attr,void *(*start_routine) (void *), void *arg);

  thread:新线程的TID 用pthread_t ID名来创建
  attr:线程属性(线程属性如果为NULL,则会创建一个标准属性的线程,)
  start_routine:线程例程(线程例程指的是如果线程创建成功,那么该线程会立即去执行的函数)
  arg:线程的例程参数
  返回值
     成功:0
     失败:errno
  代码

struct student{   char name[20];   int id;   int phone;};void * function(void *arg){   struct student *al = ((struct student *)arg);   while(1)   {       printf("name =%s id = %d phone = %d\n",al->name,al->id,al->phone);       sleep(1);   }}int main(int argc, char **argv){	pthread_t pthread;		struct student arg;	strcpy(arg.name,"wangyisi");	arg.id = 883;	arg.phone = 123456;		int ret = pthread_create(&pthread,NULL,function,(void *)&arg);	if(ret !=0)	{             perror("pthread_ceate fail\n");	     exit(1);	}       while(1)       {          printf("wait----\n");	  sleep(1);       }	return 0;}

 3、线程属性函数

     线程属性变量的使用步骤

       (1)定义线程属性变量,并使用pthread_attr_init初始化

                int pthread_attr_init(pthread_attr_t *attr);
       (2)使用pthread_attr_setXXX来设置相关的属性
       (3)使用该线程属性变量创建相应的线程
       (4)使用pthread_attr_destroy()销毁该线程属性变量
               int pthread_attr_destroy(pthread_attr_t *attr);

     设置线程的分离属性

          1、一条线程如果可接合,意味着这条线程在退出时不会自动释放自身资源,而会成为僵尸进程,同时意味着该线程退出值可以              被 其他线程取代,因此,如果不需要某条线程的退出值,那最好将线程设置为分离状态以保证该线程不会成为僵尸线程

          2、pthread_detach(id);
          3、pthread_attr_setdetachstate
               参数
                  attr:线程属性变量
                  detachstate
                           PTHREAD_CREATE_DETACHED:分离
                           PTHREAD_CREATE_JOINABLE:接合

  代码 

#include 
#include
void *routine(void *arg){ pthread_exit(NULL);}int main(int argc, char **argv){ pthread_attr_t attr; pthread_attr_init(&attr); pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED); pthread_t tid; int i=0; for(i=0; i<10; i++) { pthread_create(&tid, &attr, routine, NULL); } pause(); return 0;}

 4、线程的退出

   void pthread_exit(void *retval);

  retval:线程退出值

5、 取消一条线程

    (1)int pthread_cancel(pthread_t thread);

             给指定的线程发送一条取消请求
  (2)thread:线程TID
  (3)设置取消状态
           int pthread_setcancelstate(int state, int *oldstate);  int pthread_setcanceltype(int type, int *oldtype);
          参数
              state新的取消状态
                    PTHREAD_CANCEL_ENABLE:使能取消请求
                    PTHREAD_CANCEL_DISABLE:关闭取消请求
             oldstate:旧的取消请求
             type:新的取消类型
                    PTHREAD_CANCEL_DEFERRED:延时响应
                    PTHREAD_CANCEL_ASYNCHRONOUS:立即响应
            oldtype:旧的取消类型

 5、线程资源回收

  int pthread_join(pthread_t thread, void **retval);

  thread:线程TID
  retval:储存线程退出值的内存的指针
  该函数要注意的几点
           如果线程退出是没有退出值,retval可以指定为NULL
           pthread_join()指定的线程如果还在运行,那么它将会阻塞等待
  代码1
 

#include 
#include
#include
#include
#include
#include
#include
void * function(void *arg){ int i = 0; while(i < 10) { printf("%d \n",i++); sleep(1); }}int main(int argc, char **argv){ pthread_t pthread; int res = pthread_create(&pthread,NULL,function,NULL); if(res != 0) { perror("create failed\n"); exit(1); } pthread_join(pthread,NULL); printf("over\n"); return 0;}

  代码2(将线程里的值返回到线程)  

#include 
#include
void *routine(void *arg){ char *s = (char *)arg; printf("argument: %s", s); sleep(1); pthread_exit("Bye-Bye!\n");}int main(int argc, char **argv){ pthread_t tid; pthread_create(&tid, NULL, routine, (void *)"testing string\n"); void *p; pthread_join(tid, &p); printf("exit value: %s", (char *)p); return 0;}

6、 线程互斥锁

(1)互斥锁安装帮助文档

   sudo apt-get install manpsges   sudo apt-get install manpsges manpsges-dev manpages-posix-dev

(2)初始化互斥锁

   int pthread_mutex_init (pthread_mutex_t *__mutex,const pthread_mutexattr_t *__mutexattr)

(3) 销毁

 int pthread_mutex_destroy (pthread_mutex_t *__mutex)

(4)尝试锁

   int pthread_mutex_trylock (pthread_mutex_t *__mutex)

(5)上锁

   int pthread_mutex_lock (pthread_mutex_t *__mutex)

(6)解锁

   int pthread_mutex_unlock (pthread_mutex_t *__mutex)

(7)代码

#include 
#include
#include
#include
#include
#include
#include
pthread_mutex_t m;void * function(void *arg){ char *msg = (char *)arg; pthread_mutex_lock(&m); while(*msg != '\0') { fprintf(stderr,"%c",*msg); usleep(100); msg++; } pthread_mutex_unlock(&m); pthread_exit(NULL);}int main(int argc, char **argv){ pthread_mutex_init(&m,NULL); pthread_t t1,t2; pthread_create(&t1,NULL,function,"AAAAAAAAAAAAAAAAAAAAA"); pthread_create(&t2,NULL,function,"BBBBBBBBBBBBBBBBBBBBB"); pthread_exit(NULL); return 0;}

 7、POSIX信号量

    A、 POSIX有名信号量

          POSIX有名信号量使用步骤

          (1)使用sem_open()来创建或打开一个有名信号量
                 sem_t *sem_open(const char *name, int oflag);  sem_t *sem_open(const char *name, int oflag,  mode_t mode, unsigned int               value);
          参数
             name:信号量的名字,必须翼正斜杠“/”开头
             oflag:
                  O_CREATE:如果该名字对应的信号量不存在,则创建
                  O_EXCL:如果该名字对应的信号量已存在,则报错
             mode:八进制读写权限,0666
             value:初始化
    (2)使用sem_wait()和sem_post()来分别进行P操作和V操作
                int sem_wait(sem_t *sem);将信号量的值减1
                int sem_post(sem_t *sem);将信号量的值加1
    (3)使用sem_close()来关闭它
             int sem_close(sem_t *sem);
             sem:信号量指针
    (4)使用sem_unlink()来删除它,并释放系统资源
              int sem_unlink(const char *name);
              name:信号量名字

 B、 POSIX匿名信号量

 使用步骤
    (1)在这些线程都能访问到的区域定义这种变量(如全局变量)类型搜索sem_t
    (2)在任何线程使用它之前,用sem_init()初始化它
             int sem_init(sem_t *sem, int pshared, unsigned int value);
             参数
                 sem:信号量指针
                 pshared:该信号量的作用范围:0为线程间,非0为进程间
                 value:初始值
    (3)使用sem_wait()/sem_trywait()和sem_post()来分别进行P操作和V操作
      (4)不需要时,使用sem_destroy()来销毁
           int sem_destroy(sem_t *sem);
   代码
 

#include 
#include
#include
#include
#include
#include
#include
#include
#include
sem_t space,data;void * function(void *arg){ char *buf = (char *)arg; while(1) { sem_wait(&data);//等待有数据,就往里面读 printf("bytes:%d\n",strlen(buf)); sem_post(&space);//读完后空间就加1 }}int main(int argc, char **argv){ sem_init(&space,0,1); sem_init(&data,0,0); char buf[32]; pthread_t tid; pthread_create(&tid,NULL,function,(void *)buf); while(1) { sem_wait(&space);//等待有空间就往内存写数据 bzero(buf,32); fgets(buf,32,stdin); sem_post(&data);//数据就加1 } return 0;}

  C、使用情况

           进程间通信用到同步用到有名信号量,线程间通信同步用到匿名信号量

 

8、线程条件变量

  (1)初始化条件变量
   pthread_cond_init(pthread_cond_t *restrict,const pthread_condaddr_t *restrict attr)
   cond :条件变量 可以用pthread_cond_t+名
   attr:条件变量的属性,一般始终为0
  (2)销毁条件变量
   pthread_cond_destroy(pthread_cond_t *cond)
   cond :条件变量 可以用pthread_cond_t+名
  (3)等待条件资源
   pthread_cond_wait(pthread_cond_t *restrict,pthread_mutex_t *restrict mutex)
   cond :条件变量 可以用pthread_cond_t+名
   murex:互斥锁
 (4) 发送信号资源
   pthread_cond_signal(pthread_cond_t *cond)
  广播唤醒信号(唤醒所有等待线程)
   pthread_cond_broadcast(pthread_cond_t *cond)
 

转载地址:http://dezwi.baihongyu.com/

你可能感兴趣的文章
matlab中cuda编程中分配grid和block dimension的时候的注意事项
查看>>
GPU CUDA and MEX Programming
查看>>
arrayfun用法
查看>>
矩阵积分
查看>>
optimization on macOS
查看>>
Template-Based 3D Model Fitting Using Dual-Domain Relaxation
查看>>
install libfreenect2 on ubuntu 16.04
查看>>
how to use automake to build files
查看>>
using matlab drawing line graph for latex
查看>>
How package finding works
查看>>
build opencv3.3.0 with VTK8.0, CUDA9.0 on ubuntu9.0
查看>>
how to compile kinfu_remake with cuda 9.0 opencv2.4.13.4
查看>>
qtcreator4.4.1中cmake 与cmake3.5.1本身generate出来的setting是有区别的解决方法
查看>>
ubuntu下解决csdn网页打不开的问题
查看>>
MySQL server has gone away 问题的解决方法
查看>>
MySQL十大优化技巧
查看>>
PHP中文件读写操作
查看>>
php开发常识b_01
查看>>
PHP单例模式
查看>>
PHP项目设计
查看>>