进程线程的一些知识点

基本区别

一个进程可以有多个线程。
一个任务是一个进程。
进程之间通信用socket。线程之间共享内存。

一、进程相关问题

包括进程间的通信,

1、进程间通信

包括管道,共享内存,消息队列,信号量

(1)管道

管道可以处理类似如下的行为:
grep "aaa" 1.txt | grep yyy
也就是说把grep "aaa" 1.txt 的结果发送给grep yyy去使用。(无名管道)

a. 无名管道

只能在公共的祖先的进程间使用管道。
一般 A 使用 pipe(int fd[2]);然后A在fork出一个子进程,这两个进程之间可以用管道进行通信。注意需要关闭子进程的写端,关闭父进程的读端。

linux pipe 源码分析
SYSCALL_DEFINE2(pipe2, int __user *, fildes, int, flags)
{
    return do_pipe2(fildes, flags);
}

SYSCALL_DEFINE1(pipe, int __user *, fildes)
{
    return do_pipe2(fildes, 0);
}

上面两个程序被调用的时候会自动变成SYS_pipe2(int fd[2],int flags) 和 sys_pipe(fd[2]),这个是宏定义替换的结果,可以参考Linux系统调用之SYSCALL_DEFINE。这两种pipe的区别是参数不一样,但是他们都会调用do_pipe2(int fd[],int flags);

static int do_pipe2(int __user *fildes, int flags)
{
    struct file *files[2];
    int fd[2];
    int error;

    error = __do_pipe_flags(fd, files, flags);
    if (!error) {
        //执行else可能性大,使用unlikely可以预存cathe增加程序执行速度,
        //copy_to_user为了把内核态的fd存到用户态中,成功返回0,失败返回失败数目
        if (unlikely(copy_to_user(fildes, fd, sizeof(fd)))) {
            fput(files[0]);
            fput(files[1]);
            put_unused_fd(fd[0]);
            put_unused_fd(fd[1]);
            error = -EFAULT;
        } else {
            //把文件读写和fd绑定
            fd_install(fd[0], files[0]);
            fd_install(fd[1], files[1]);
        }
    }
    return error;
}

static int __do_pipe_flags(int *fd, struct file **files, int flags)
{
    int error;
    int fdw, fdr;

    if (flags & ~(O_CLOEXEC | O_NONBLOCK | O_DIRECT))
        return -EINVAL;
            //获取文件描述符
    error = create_pipe_files(files, flags);
    if (error)
        return error;
    //获取可用的文件描述fdr
    error = get_unused_fd_flags(flags);
    if (error < 0)
        goto err_read_pipe;
    fdr = error;

    error = get_unused_fd_flags(flags);
    if (error < 0)
        goto err_fdr;
    fdw = error;

    audit_fd_pair(fdr, fdw);
    //绑定文件描述符
    fd[0] = fdr;
    fd[1] = fdw;
    return 0;

 err_fdr:
    put_unused_fd(fdr);
 err_read_pipe:
    fput(files[0]);
    fput(files[1]);
    return error;
}

int create_pipe_files(struct file **res, int flags)
{
    struct inode *inode = get_pipe_inode();
    struct file *f;

    if (!inode)
        return -ENFILE;

    f = alloc_file_pseudo(inode, pipe_mnt, "",
                O_WRONLY | (flags & (O_NONBLOCK | O_DIRECT)),
                &pipefifo_fops);
    if (IS_ERR(f)) {
        free_pipe_info(inode->i_pipe);
        iput(inode);
        return PTR_ERR(f);
    }

    f->private_data = inode->i_pipe;
    //读
    res[0] = alloc_file_clone(f, O_RDONLY | (flags & O_NONBLOCK),
                  &pipefifo_fops);
    if (IS_ERR(res[0])) {
        put_pipe_info(inode, inode->i_pipe);
        fput(f);
        return PTR_ERR(res[0]);
    }
    res[0]->private_data = inode->i_pipe;
    //写
    res[1] = f;
    stream_open(inode, res[0]);
    stream_open(inode, res[1]);
    return 0;
}

之后linux再使用管道的时候,就会根据上述代码绑定fd[0],fd[1]到文件描述符file,然后进程a输出到fd[1],进程b从fd[0]读取

为什么无名pipe必须在具有亲缘关系的进程中执行?

因为文件描述符在不同的进程之间是不同的,而在父子进程间,子进程会继承父进程的所有文件描述符(子进程不close,描述符表的数目不会为0,文件不会关闭,此处有坑

1、父进程和子进程可以共享打开的文件描述符。
2、父子进程共享文件描述符的条件:在fork之前打开文件。
3、对于两个完全不相关的进程,文件描述符不能共享。
4、父子进程文件描述符是共享的,但是关闭的时候可以分别关闭,也可以同时在公有代码中关闭。
转自Linux中fork的使用(05)---父子进程共享文件描述符

b. 有名管道

有名管道采用的是使用一个文件来存中间数据,然后两个进程一个只写,另一个只读。可以有多个读和写。

(2) 共享内存

多个进程可以共享相同的物理地址从而实现数据交换。

劣势,该种方法无法保证同步,需要信号量来控制。

(3) 消息队列

消息队列 在linux内核中是一个链表,可以双向读写。和java中使用kafka很类似,原理都是把数据写到一个队列中,然后控制读取就可以了。
双向队列的实现:

进程A,B
队列 C
标识 1,2
A写1到C,B只读1从C中
B写2到C,A只读2从C中

(4)信号量

信号量 说白了就是计数器,目的是为了控制共享资源的访问。
信号量每次可以多个进程执行,而锁只能一个进程执行。
跟设定的参数有关。
比如资源有4分,那么可以同时让4个进程使用资源。

PV操作。

P操作:使 S=S-1 ,若 S>=0 ,则该进程继续执行,否则排入等待队列。

V操作:使 S=S+1 ,若 S>0 ,唤醒等待队列中的一个进程。

1)一个生产者,一个消费者,公用一个缓冲区。

可以作以下比喻:将一个生产者比喻为一个生产厂家,如伊利牛奶厂家,而一个消费者,比喻是学生小明,而一个缓冲区则比喻成一间好又多。第一种情况,可以理解成伊利牛奶生产厂家生产一盒牛奶,把它放在好又多一分店进行销售,而小明则可以从那里买到这盒牛奶。只有当厂家把牛奶放在商店里面后,小明才可以从商店里买到牛奶。所以很明显这是最简单的同步问题。

解题如下:

定义两个同步信号量:

empty——表示缓冲区是否为空,初值为1。

full——表示缓冲区中是否为满,初值为0。

生产者进程

while(TRUE){

生产一个产品;

     P(empty);

     产品送往Buffer;

     V(full);

}

消费者进程

while(TRUE){

P(full);

   从Buffer取出一个产品;

   V(empty);

   消费该产品;
————————————————
版权声明:本文为CSDN博主「csjinzhao」的原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/u014174955/article/details/44022391

二、线程相关问题

1. 线程的用处

a. 线程可以充分利用多核

可以以此介绍并行的优势,来说明线程可以加速任务执行。

2. 线程的特点

线程是进程的一部分,进程之间可以一般不共享内存,而线程之间是共享内存的。共享内存的好处就是效率高但是却需要锁的机制。

3. 线程之间的锁

该部分在多线程中讨论

进程和线程效率问题

进程切换比线程切换时间长
原因是采用进程切换需要保存的上下文比较多(栈帧,内存,寄存器等)
而对于线程来说,是保存的部分进程中的上下文,自然进程切换要的时间和资源比较长。

猜你喜欢

转载自www.cnblogs.com/clnsx/p/12313311.html