(三)进程基本原理和概念

进程基本原理和概念

进程:

进程ID:标识进程的唯一数字父进程的ID(PPID),启动进程的用户ID(UID)

进程互斥:若干进程都要使用某一共享资源时,任何时刻最多允许一个进程使用,其他要使用必须 等待,直到占用资源被释放。

临界资源:操作系统中将一次只允许一个进程访问的资源称为临界资源。

临界区:进程中访问临界资源的那段程序代码空间称为临界区。需要保证进程互赤的进入各自的临界区。

进程同步:一组并发进程按照一定的顺序执行的过程称为进程间的同步。

进程调度:按一定的短发,从一组待运行的进程中选出一个来占有CPU运行。{抢占式、非抢占式两种,1.先来先服务调度;2.端进程优先调度;3.高优先级先服务调度;4.时间片轮转法调度;}

死锁:多个进程因竞争资源而形成一种僵局,若无外力作用,这些进程将永远不能再向前推进。

进程调度
时间片完
I/O完成
I/O请求
就绪
执行
阻塞

— 进程控制编程—

  1. 获取进程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;
    }
    
  2. 进程创建-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");
    }
    
  3. 进程等待

    进程相关的头文件

    #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);
    }
    

— 进程通信设计—

  1. 无名管道(管道)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);
            
        }
    ]
    
  2. 有名管道 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的文件操作)。

    1. 没有使用O_NONBLOCK:访问要求无法满足时进程将阻塞,如试图读取一个空的FIFO,将导致进程阻塞。

    2. 使用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();//暂停,等待信号。    
    }
    
  3. 信号通信 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)

    1. pid>0

      将信号发送给进程ID为pid的进程

    2. pid == 0

      将信号发送给同组的进程

    3. pid < 0

      将信号发送给其进程组ID等于pid的绝对值的进程

    4. pid == -1

      将信号发送给所有的进程。

  4. 信号通信 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. 共享内存通信方式

    共享内存是在进程间共享数据的一种最快的方法,一个进程向共享内存区域写入数据,共享这个内存区域的所有进程就可以立即看到其中的内容。

    使用步骤:

     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);
        }
    }
    

— 进程间通信(二)

  1. 消息队列

    相关头文件
    #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);
    }
    
  2. 信号量

    相关头文件
    #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;
    }
    
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
程序、进程、线程是计算机中非常重要的概念,它们之间的关系如下: 程序是指一组指令和数据的集合,是一种静态的概念,通常以文件的形式存在于存储设备中,需要被载入内存并被操作系统调度才能执行。 进程是指操作系统中正在运行的一个程序的实例,是一种动态的概念,包括程序计数器、寄存器、内存、文件句柄等运行时状态。每个进程都有自己的地址空间、资源和权限,操作系统通过进程调度机制来管理和调度进程的执行。 线程是指进程中独立运行的一组指令序列,是操作系统调度的基本单位,它们共享进程的地址空间和资源,但每个线程有自己的栈和寄存器。线程可以并发执行,提高系统的并行度和效率。 它们之间的区别如下: 1. 程序是静态的代码和数据的集合,进程是程序的执行实例,而线程是进程中的一个执行序列。 2. 进程具有独立的地址空间和系统资源,而线程共享进程的地址空间和资源,但具有独立的栈和寄存器。 3. 进程之间的切换需要保存和恢复进程的所有状态和资源,而线程之间的切换只需要保存和恢复线程的栈和寄存器。 4. 进程之间的通信需要通过IPC(进程间通信)机制,而线程之间的通信可以通过共享内存或消息传递等方式。 总之,程序、进程和线程是计算机中非常重要的概念,它们之间有着密切的联系和区别,对于理解操作系统的工作原理和编写高效的多线程应用程序都非常重要。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值