linux进程间的那些事儿

北城以北 提交于 2020-03-09 16:25:23

写这篇文章之前,我对linux的进程间通讯还是有些畏惧的,不过看了一些其它文章之后,觉得linux进程间远比我学到的要难得多,首先来说,linux下线程的概念被淡化了,线程又名轻量级进程。线程机制是现代编程技术中常用的一种抽象,提供了在同一程序中共享内存地址控件的一组线程。这些线程可以共享打开的文件和其它资源。线程机制支持并发程序涉及技术,可以保证真正并行处理。linux实现线程的机制非常独特,从内核的角度来说,没有线程这个概念,把所有线程当成进程来实现,内核并没有准备特别的数据结构来表示线程。相反,线程仅仅被视为一个与其它进程共享某些资源的进程,每个线程都拥有唯一率属于自己的task_struct,所以在内核中,看起来像一个普通的进程(只是该级才能哼和其它一些进程共享某些资源,如地址空间).

在windows或是sun solaris等操作系统中,提供了专门支持线程的机制,线程被抽象成一种耗费较少资源,执行迅速的单元。而对于linux来说,它只是一种进程间共享资源的手段。linux线程的创建和普通进程创建类似,只不过在调用clone的时候需要传递一些参数来指明需要共享的资源

clone(CLONE_VM|CLONE_FS|CLONE_FILES|CLONE_SIGHAND,0);

而一个普通的fork实现

clone(SIGCHLD,0)

而vfork的实现是

clone(CLONE_VFORK|CLONE_VM|SIGCHLD,0);

传递给clone()的参数标志决定了新创建进程的行为方式和父子进程之间共享的资源的种类

而至于exec()则是用来指定程序替换当前进程的所有内容,所以exec()系列函数经常在前三个函数使用之后调用,来创建一个全新的程序运行环境。fork的调用,一次返回2次,分别在父进程和子进程中返回,父进程中其返回值是紫禁城的进程描述符,子进程中其返回值是0.fork创建一个进程时,子进程复制其父进程的数据段,堆栈,共享父进程的代码段,为了降低开销,linux采用了写时复制技术,只有在写入的时候,才复制写入的那块内存,一般为虚拟内存的一页。

vfork则是保证子进程先运行,即在调用exec和exit之前进程不运行,也就是如果子进程依赖父进程的进一步动作,将陷入死锁。再调用exec和exit之前,子进程和父进程时公用代码段和数据段的。

进程间通讯

管道以及命名管道,管道可用于具有亲缘关系进程间通讯,命名管道克服了管道无名的限制,可以允许无亲缘关系进程间通讯。

int main()
{
    int fd[2];
    pid_t pid;
    char line[1024];
    int nRead = 0;
    if(pipe(fd) != 0)
    {
        exit(0);
    }
    if(pid = fork() < 0)
    {
        exit(1);
    }
    else if(pid > 0)
    {
        close(fd[0]);
        write(fd[1],"\nhelloworld\n",14);
    }
    else
    {
        close(fd[1]);
        nRead = read(fd[0],line,1024);
        write(STDOUT_FILENO,line,nRead);
    }
    return 0;
}

信号,用于通知接受进程有某件事件发生,除了用于进程间通讯外,进程还可以发送信号给进程本身

#include <unistd.h>
#include <sys/types.h>
#include <stdlib.h>
#include <stdio.h>
#include <signal.h>

static int alarm_fired = 0;

void sig_test(int sig)
{
    alarm_fired = 1;
}

int main()
{
    pid_t pid;
    pid = fork();
    switch(pid)
    {
    case -1:
        exit(1);
    break;
    case 0:
        sleep(5);
        kill(getppid(),SIGALRM);
        exit(0);
    break;
    }
    signal(SIGALRM,sig_test);    
    while(!alarm_fired)
    {
        printf("wait signal\n");
        sleep(1);
    }
    if(alarm_fired)
    {
        printf("\nI got a signal %d\n",SIGALRM);    
    }
    exit(0);
}

消息队列 消息队列时消息的链表,包括posix消息队列和system V消息队列,有足够的进程可以向队列中添加消息,被赋予读权限的进程则可以读走队列中管道消息

 

typedef struct {
    long type;
    char name[64];
    int height;
}Msg;
///process 1
int main()
{
    key_t key = ftok("/home/dou",'6');
    printf("key:%x\n",key);
    int msgid = msgget(key,IPC_CREAT|O_WRONLY|0777);
    if(msgid < 0)
        exit(-1);
    Msg msg;
    puts("please input your type name height:");
    scanf("%ld%s%d",&msg.type,msg.name,&msg.height);
    msgsnd(msgid,&msg,sizeof(msg)-sizeof(msg.type),0);
    return 0;
}

//process 2
int main()
{
    key_t key = ftok("/home/dou",'6');
    printf("key:%x\n",key);
    int msgid = msgget(key,O_RDONLY);
    if(msgid < 0)
        exit(-1);
    Msg rcv;
    long type;
    puts("please input your type you want:");
    scanf("%ld",&type);
    msgrcv(msgid,&rcv,sizeof(rcv)-sizeof(type),type,0);
    msgctl(msgid,IPC_RMID,NULL);
    return 0;
}

共享内存,使得多个进程可以访问同一块内存空间,是最快的可用IPC形式。是针对其它通信机制运行效率较低而设计的,往往与其他通信机制,如信号量结合使用,来达到进程间的同步以及互斥。

#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <sys/shm.h>


#define MAX_BUFFER_LEN 2048
struct shared_use_st
{  
    int written;//作为一个标志,非0:表示可读,0表示可写 
    char text[MAX_BUFFER_LEN ];//记录写入和读取的文本
};

//process read
int main()
{
    int running = 1;
    void *shm = nullptr;
    struct shared_use_st *shared;
    int shmid;
    shmid = shmget((key_t)1234,sizeof(struct shared_use_st),0666|IPC_CREAT);
    if(shmid == -1)
    {
        exit(0);
    }
    printf("\nMemory attached at %x\n",(int)shm);
    shared = (struct shared_use_st *)shm;
    shared->written = 0;
    while(running)
    {
        if(shared->written != 0)
        {
            printf("You wrote:%s",shared->text);
            sleep(rand()%3);
            shared->written = 0;
            if(strncmp(shared->text,"end",3) == 0)
            {
                running = 0;
            }
            else
            {
                sleep(1);
            }
        }
    }
    if(shmdt(shm) == -1)
    {
        exit(-1);
    }
    if(shmctl(shmid,IPC_RMID,0) == -1)
    {
        exit(-2);    
    }
    return 0;
}

//process write
int main()
{
    int running = 1;
    void *shm = nullptr;
    struct shared_use_st *shared = nullptr;
    char buffer[1024];
    int shmid;
    shmid = shmget((key_t)1234,sizeof(struct shared_use_st),0666|IPC_CREAT);
    if(shmid == -1)
        return -1;
    shm = shmat(shmid,(void *)0,0);
    if(shm == (void *)-1)
    {
        return -1;    
    }
    printf("Memory attached at %x\n",(int)shm);
    shared = (struct shared_use_st*)shm;
    while(running)
    {
        while(shared->written == 1)
        {
            sleep(1);
            printf("Waiting...\n");
            
        }
        printf("Enter some text:");
        fgets(buffer,1024,stdin);
        strncpy(shared->text,buffer,1024);
        shared->written = 1;
        if(strncmp(buffer,"end",3) == 0)
            running = 0;
    }
    if(shmdt(shm) == -1)
    {
        fprintf(stderr,"shmdt failed\n");
        return -1;
    }
    return 0;
}

信号量  主要作为进程间以及同一进程不同线程之间的同步手段

#include <stdio.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>

#define PATHNAME "."
#define SEM_TEST_ID   0x5555

union semun
{
    int val;
    struct semid_ds *buf;
    unsigned short *array;
    struct seminfo *_buf;
};

int createsemset(int nums,int flags)
{
    key_t key = ftok(PATHNAME,SEM_TEST_ID);
    if(key < 0)
        return -1;
    int semid = semget(key,nums,flags);
    if(semid < 0)
        return -1;
    retur semid;
}

int initsem(int semid,int nums,int initval)
{
    union semun _un;
    _un.val = initval;
    if(semctl(semid,nums,SETVAL,_un) < 0)
        return -1;
    return 0;
}

int getsemset(int nums)
{
    return createsemset(nums,IPC_CREAT);
}

int commPV(int semid,int who,int op)
{
    struct sembuf _buf;
    _buf.sem_num = who;
    _buf.sem_op = op;
    _buf.sem_flg = 0;
    if(semop(semid,&buf,1)<0)
        return -1;
    return 0;
}
int destorysemset(int semid)
{
    if(semctl(semid,0,IPC_RMID)<0)
        return -1;
    return 0;
}

int main()
{
    int semid = createsemset(1,IPC_CREAT|IPC_EXCL|0666);
    initsem(semid,0,0);
    pid_t pid = fork();
    if(pid == 0)
    {
        int semid = getsemset(0);
        while(1){
            printf("A");
            fflush(stdout);
            usleep(101000);
            printf("A ");
            fflush(stdout);
            usleep(100000);
        }
    }
    else
    {
        while(1){
            printf("B");
            fflush(stdout);
            usleep(100100);
            printf("B ");
            fflush(stdout);
            usleep(120000);
        }
        wait(nullptr);
    }
    destorysemset(semid);
}

最后为最经典的套接字通讯。

下面来回忆一下关于线程的一些内容

#include <pthread.h>

pthread_t pid;

void *func(void *arg);

pthread_create(pid,nullptr,func,nullptr); //创建一线程

pthread_exit(0);                                    //运行结束

pthread_join(pid);                                 //阻塞等待子线程,防止主线程结束,子线程没机会运行,同时获取子线程的退出状态

pthread_detach();                                //线程分离,运行结束后,资源会被立刻收回

子线程调用

pthread_detach(pthread_self());   //非阻塞立即返回。子线程分为分离状态和join状态,join状态子线程运行结束,资源不会释放,调用pthread_join之后,资源才释放,分离状态的子线程运行结束后,资源被自动回收。

所有线程可以直接共享内存和变量等,线程的栈是单独分配的,但是也是在同一地址空间,与多进程相比,线程需要同步需要锁。

linux提供的基本同步机制

互斥量  互斥量本质上是一把锁,在访问共享资源前对互斥量加锁,访问完释放锁。对互斥量加锁后,任何其它试图再次对互斥量加锁的线程会被阻塞直到当前线程释放该互斥锁,如果释放互斥锁时有多个线程阻塞,所有在该互斥锁上的阻塞线程都会编程可运行状态,第一个变为运行状态的线程可以对互斥量加锁,其它等待。

int pthread_mutex_init(pthread_mutex_t *m,pthread_mutexattr_t *attr);

int pthread_mutex_destory(pthread_mutex_t *);

int pthread_mutex_lock(pthread_mutex_t *m);

int pthread_mutex_unlock(pthread_mutex_t *m);

自旋锁  当临界区是一段很小的代码,线程进入临界区,马上就会退出。这时使用互斥量,会导致大量的上下文切换,所以自旋锁出现了,此时等待自旋锁时,进程不会释放cpu,而是一直占用cpu,所以自旋锁使用情况是临界区的代码不会执行时间太长,而且竞争不是太激烈。否则大量等待自旋锁的线程就像死锁一样。

#include <spinlock.h>

void spin_lock_init(spinlock_t *lock);

void spin_lock(spinlock_t *lock);

void spin_unlock(spinlock_t *lock);

读写锁

读写锁与互斥量类似,不过读写锁允许更高的并行性。互斥量要么是锁住状态,要么是不加锁状态,而且一次只有一个线程可以对其加锁。

读写锁可以由三种状态,读模式下加锁状态,写模式下加锁状态,不加锁状态。一次只有一个线程可以占用写模式的读写锁,但是多线程可以同时占有读模式的读写锁。在读写锁写加锁状态下,解锁之前,所有试图对这个锁加锁的线程都会被阻塞,当读加锁状态时,所有试图以读模式对它进行加锁的线程都可以得到访问权,但是如果线程希望写模式对此锁进行加锁,必须阻塞到所有的线程释放读锁。

#include <pthread.h>

int pthread_rwlock_init(pthread_rwlock_t *rwlock,const pthread_rwlockattr_t *attr);

int pthread_rwlock_destory(pthread_rwlock_t *rwlock);

pthread_rwlock_rdlock(pthread_rwlock_t *rwlock);

pthread_rwlock_wrlock(pthread_rwlock_t *rwlock);

pthread_rwlock_unlock(pthread_rwlock_t *rwlock);

条件变量  与互斥量一起使用时,允许线程等待特定的条件发生。

如果线程正在等待共享数据内某个条件出现,代码可以反复对互斥对象锁定和解锁,以检查值的任何变化,同时,还要快速将会吃对象解锁,以便其它线程能够进行任何必须的更改。需要一种方法以唤醒因等待特定条件而睡眠的线程

#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
pthread_cond_t cond = PTHREAD_COND_INITIALIZER;
void *thread1(void *);
void *thread2(void *);

int i = 1;
int main()
{
    pthread_t a,b;
    pthread_create(&a,nullptr,thread1,nullptr);
    pthread_create(&b,nullptr,thread2,nullptr);
    pthread_join(b,nullptr);
    pthread_mutex_destory(&mutex);
    pthread_cond_destory(&cond);
    exit(0);
}

void *thread1(void *arg)
{
    for(i = 1;i<9;i++)
    {
        pthread_mutex_lock(&mutex);
        if(i%3 == 0)
            pthread_cond_signal(&cond);
        else
            printf("thread1:%d\n",i);
        pthread_mutex_unlock(&mutex);
        sleep(1);
    }
}

void *thread2(void *arg)
{
    while(i<9)
    {
        pthread_mutex_lock(&mutex);
        if(i%3 != 0)
            pthread_cond_wait(&cond,&mutex);
        printf("thread2:%d\n",i);
        pthread_mutex_unlock(&mutex);
        sleep(1);
    }
}

pthread_cond_signal 仅仅负责唤醒正在阻塞在同一条件变量上的一个线程,如果存在多个线程,系统自动根据调度策略决定唤醒其中的一个线程,在多处理器上,该函数时可能同时唤醒多个线程,同时该函数与锁操作无关。

信号量  允许多个进程进入临界区

int sem_init(sem_t *sem,int pshared,unsigned int value);  //初始化指定数量的信号量,pshared表示进程间共享还是线程共享

int sem_destory(sem_t *sem);                                           //信号量销毁

int sem_wait(sem_t *sem);                                               //等待信号量,如果信号量的值大于0,则减一立即返回。

int sem_post(sem_t *sem);                                              //释放信号量,让信号量的值加1

 

写了挺多,但是不是太难,都是基础的东西,个人感觉容易理解,但是要凭借他构建高楼大厦,好像还是欠缺一些。后续会补上,加油

 

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!