public inbox for linux-kernel@vger.kernel.org
 help / color / mirror / Atom feed
From: Oleg Nesterov <oleg@tv-sign.ru>
To: Peter Zijlstra <a.p.zijlstra@chello.nl>
Cc: linux-kernel <linux-kernel@vger.kernel.org>,
	Prarit Bhargava <prarit@redhat.com>,
	Alan Cox <alan@lxorguk.ukuu.org.uk>
Subject: Re: [RFC][PATCH] ->signal->tty locking
Date: Tue, 17 Oct 2006 12:10:18 +0400	[thread overview]
Message-ID: <20061017081018.GA115@oleg> (raw)
In-Reply-To: <1160992420.22727.14.camel@taijtu>

On 10/16, Peter Zijlstra wrote:
>
> Oleg wrote:
> "Historically ->signal/->sighand (both ptrs and their contents) were globally
> protected by tasklist_lock. 'current' can use these pointers lockless, they
> can't be changed under him.
> 
> Nowadays ->signal/->sighand are _also_ protected by ->sighand->siglock.
> Unless you are current, you can't lock ->siglock directly (without holding
> tasklist_lock), you should use lock_task_sighand()."
> 
> Then, to be consistent with the rest of the kernel, ->signal->tty
> locking should look like so:
> 
>   mutex_lock(&tty_mutex)
>     read_lock(&tasklist_lock)
>       lock_task_sighand(p, &flags)

I've also started similar patches, but have no time to finish it.

I don't think we need tasklist_lock. I think ->sighand->siglock is enough.

So do_task_stat() doesn't need to take tty_mutex at all.

However, tty_mutex protects ->tty from release_dev(tty), so it is also
possible to do:

	mutex_lock(&tty_mutex);
	tty = task->signal->tty;
	barrier();
	if (tty) {
		// ->tty could be changed/cleared from under us,
		// but it can't be released while we are holding
		// tty_mutex
		do_something(tty);
	}
	...

But first I think we should kill task_lock(). This is changelog for
the first patch in unfinished series:

	Taking task_lock() when updating/using signal->tty doesn't help because

		- it is used only in some random places

		- signal->tty is per-process, while ->alloc_lock is per-thread

	We can improve this if we take task_lock(task->group_leader), but I think
	this is not the best option and we should use sighand->siglock instead,
	because

		- task_lock() interacts badly with write_lock_irq(&tasklist_lock),
		  sys_setsid() won't be happy.

		- unless we are 'current' or tasklist_lock is held, we anyway need
		  ->siglock to access ->signal. Actually, even reading ->group_leader
		  is not safe unless we know the task was not released.

		- most of signal_struct's contents is already protected by ->siglock,
		  why ->tty isn't?

> However, lock_task_sighand(), is a conditional lock, p might not have a
> ->sighand, in which case it returns NULL. What would that mean for
> ->signal, can I then still modify it?

This means the task has already dead, it doesn't have ->signal.

> struct sighand_struct *sighand = lock_task_sighand(p, &flags);
> if (sighand) {
> 	/* modify/use ->signal->tty */
> 	unlock_task_sighand(p, &flags);
> } else
> 	/* now what !? */

see above.

> The same problem appears in fs/proc/array.c:do_task_stat(), there the
> locking doesn't look quite right either.

Hmm. I think do_task_stat() is fine, it does nothing when lock_task_sighand()
fails.

> --- linux-2.6.orig/drivers/char/tty_io.c
> +++ linux-2.6/drivers/char/tty_io.c
> @@ -63,6 +63,12 @@
>   *
>   * Move do_SAK() into process context.  Less stack use in devfs functions.
>   * alloc_tty_struct() always uses kmalloc() -- Andrew Morton <andrewm@uow.edu.eu> 17Mar01
> + *
> + * A word on (struct task)::signal->tty locking
> + *
> + *   mutex_lock(&tty_mutex)
> + *     read_lock(&tasklist_lock)
> + *       lock_task_sighand()
>   */
>  
>  #include <linux/types.h>
> @@ -1282,6 +1288,7 @@ static void do_tty_hangup(void *data)
>  	struct task_struct *p;
>  	struct tty_ldisc *ld;
>  	int    closecount = 0, n;
> +	unsigned long flags;
>  
>  	if (!tty)
>  		return;
> @@ -1350,20 +1357,26 @@ static void do_tty_hangup(void *data)
>  	  This should get done automatically when the port closes and
>  	  tty_release is called */
>  	
> +	mutex_lock(&tty_mutex);

I am not sure it is needed.

>  	read_lock(&tasklist_lock);
>  	if (tty->session > 0) {
>  		do_each_task_pid(tty->session, PIDTYPE_SID, p) {
> +			lock_task_sighand(p, &flags);
>  			if (p->signal->tty == tty)
>  				p->signal->tty = NULL;
> +			unlock_task_sighand(p, &flags);

We don't need lock_task_sighand() here, we can use spin_lock_irq(->siglock).

We are holding tasklist_lock. This means that all tasks found by
do_each_task_pid() have a valid ->signal/->sighand != NULL.
tasklist_lock protects against release_task()->__exit_signal() and
from changing ->sighand by de_thread().

> @@ -1506,14 +1522,27 @@ void disassociate_ctty(int on_exit)
>  
>  	/* Must lock changes to tty_old_pgrp */
>  	mutex_lock(&tty_mutex);
> +	lock_task_sighand(current, &flags);

Again, we can use spin_lock_irq(current->signal->siglock). It is safe to
use current->sighand directly, it can't be freed or changed from under us.

>  	current->signal->tty_old_pgrp = 0;
> -	tty->session = 0;
> -	tty->pgrp = -1;
> +
> +	/* It is possible that do_tty_hangup has free'd this tty */
> +	if (current->signal->tty) {
> +		current->signal->tty->session = 0;
> +		current->signal->tty->pgrp = 0;
> +	} else {
> +#ifdef TTY_DEBUG_HANGUP
> +		printk(KERN_DEBUG "error attempted to write to tty [0x%p]"
> +		       " = NULL", tty);
> +#endif
> +	}
> +	unlock_task_sighand(current, &flags);
>  
>  	/* Now clear signal->tty under the lock */
>  	read_lock(&tasklist_lock);
>  	do_each_task_pid(current->signal->session, PIDTYPE_SID, p) {
> +		lock_task_sighand(p, &flags);
>  		p->signal->tty = NULL;
> +		unlock_task_sighand(p, &flags);
>  	} while_each_task_pid(current->signal->session, PIDTYPE_SID, p);
>  	read_unlock(&tasklist_lock);
>  	mutex_unlock(&tty_mutex);
> @@ -2340,11 +2369,15 @@ static void release_dev(struct file * fi
>  
>  		read_lock(&tasklist_lock);
>  		do_each_task_pid(tty->session, PIDTYPE_SID, p) {
> +			lock_task_sighand(p, &flags);
>  			p->signal->tty = NULL;
> +			unlock_task_sighand(p, &flags);
>  		} while_each_task_pid(tty->session, PIDTYPE_SID, p);
>  		if (o_tty)
>  			do_each_task_pid(o_tty->session, PIDTYPE_SID, p) {
> +				lock_task_sighand(p, &flags);
>  				p->signal->tty = NULL;
> +				unlock_task_sighand(p, &flags);
>  			} while_each_task_pid(o_tty->session, PIDTYPE_SID, p);
>  		read_unlock(&tasklist_lock);

While doing a similar changes, I introduced a couple of simple
helpers:

	static inline void proc_clear_tty(struct task_struct *p)
	{
		spin_lock_irq(&p->sighand->siglock);
		p->signal->tty = NULL;
		spin_unlock_irq(&p->sighand->siglock);
	}

	static void session_clear_tty(pid_t session)
	{
		struct task_struct *p;

		do_each_task_pid(session, PIDTYPE_SID, p) {
			proc_clear_tty(p);
		} while_each_task_pid(session, PIDTYPE_SID, p);
	}

I think it makes sense.

>  static int tiocsctty(struct tty_struct *tty, int arg)
>  {
>  	struct task_struct *p;
> +	unsigned long flags;
>  
>  	if (current->signal->leader &&
>  	    (current->signal->session == tty->session))
> @@ -2898,6 +2940,7 @@ static int tiocsctty(struct tty_struct *
>  	 */
>  	if (!current->signal->leader || current->signal->tty)
>  		return -EPERM;
> +	mutex_lock(&tty_mutex);
>  	if (tty->session > 0) {
>  		/*
>  		 * This tty is already the controlling
> @@ -2910,20 +2953,23 @@ static int tiocsctty(struct tty_struct *
>  
>  			read_lock(&tasklist_lock);
>  			do_each_task_pid(tty->session, PIDTYPE_SID, p) {
> +				lock_task_sighand(p, &flags);
>  				p->signal->tty = NULL;
> +				unlock_task_sighand(p, &flags);
>  			} while_each_task_pid(tty->session, PIDTYPE_SID, p);
>  			read_unlock(&tasklist_lock);
> -		} else
> +		} else {
> +			mutex_unlock(&tty_mutex);
>  			return -EPERM;
> +		}
>  	}
> -	mutex_lock(&tty_mutex);
> -	task_lock(current);
> -	current->signal->tty = tty;
> -	task_unlock(current);
> -	mutex_unlock(&tty_mutex);
> -	current->signal->tty_old_pgrp = 0;
>  	tty->session = current->signal->session;
>  	tty->pgrp = process_group(current);
> +	lock_task_sighand(current, &flags);
> +	current->signal->tty = tty;
> +	current->signal->tty_old_pgrp = 0;
> +	unlock_task_sighand(current, &flags);
> +	mutex_unlock(&tty_mutex);
>  	return 0;
>  }

There is a very similar code in tty_open(), probably we need another
helper, proc_set_tty().

But I am not sure about locking. I think we should check
->signal->leader/->signal->tty and set ->tty in proc_set_tty()
under ->siglock, this way we can remove tty_mutex from sys_setsid().

Oleg.


  parent reply	other threads:[~2006-10-17  8:10 UTC|newest]

Thread overview: 10+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2006-10-16  9:53 [RFC][PATCH] ->signal->tty locking Peter Zijlstra
2006-10-16 13:39 ` Prarit Bhargava
2006-10-17  8:10 ` Oleg Nesterov [this message]
2006-10-17 10:17   ` Peter Zijlstra
2006-10-17 12:33     ` Oleg Nesterov
2006-10-17 13:00       ` Peter Zijlstra
2006-10-17 14:08         ` Alan Cox
2006-10-18 16:55         ` Oleg Nesterov
2006-10-17 13:29       ` Alan Cox
2006-10-18 17:21         ` Oleg Nesterov

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=20061017081018.GA115@oleg \
    --to=oleg@tv-sign.ru \
    --cc=a.p.zijlstra@chello.nl \
    --cc=alan@lxorguk.ukuu.org.uk \
    --cc=linux-kernel@vger.kernel.org \
    --cc=prarit@redhat.com \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox