2006-07-14 11:24:40 +04:00
/*
* taskstats . c - Export per - task statistics to userland
*
* Copyright ( C ) Shailabh Nagar , IBM Corp . 2006
* ( C ) Balbir Singh , IBM Corp . 2006
*
* This program is free software ; you can redistribute it and / or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation ; either version 2 of the License , or
* ( at your option ) any later version .
*
* This program is distributed in the hope that it will be useful ,
* but WITHOUT ANY WARRANTY ; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE . See the
* GNU General Public License for more details .
*
*/
# include <linux/kernel.h>
# include <linux/taskstats_kern.h>
2006-10-01 10:28:55 +04:00
# include <linux/tsacct_kern.h>
2006-07-14 11:24:41 +04:00
# include <linux/delayacct.h>
2006-10-01 10:28:58 +04:00
# include <linux/tsacct_kern.h>
2006-07-14 11:24:47 +04:00
# include <linux/cpumask.h>
# include <linux/percpu.h>
2006-07-14 11:24:40 +04:00
# include <net/genetlink.h>
# include <asm/atomic.h>
2006-07-14 11:24:47 +04:00
/*
* Maximum length of a cpumask that can be specified in
* the TASKSTATS_CMD_ATTR_REGISTER / DEREGISTER_CPUMASK attribute
*/
# define TASKSTATS_CPUMASK_MAXLEN (100+6*NR_CPUS)
2006-07-14 11:24:40 +04:00
static DEFINE_PER_CPU ( __u32 , taskstats_seqnum ) = { 0 } ;
static int family_registered ;
2006-12-07 07:33:20 +03:00
struct kmem_cache * taskstats_cache ;
2006-07-14 11:24:40 +04:00
static struct genl_family family = {
. id = GENL_ID_GENERATE ,
. name = TASKSTATS_GENL_NAME ,
. version = TASKSTATS_GENL_VERSION ,
. maxattr = TASKSTATS_CMD_ATTR_MAX ,
} ;
static struct nla_policy taskstats_cmd_get_policy [ TASKSTATS_CMD_ATTR_MAX + 1 ]
__read_mostly = {
[ TASKSTATS_CMD_ATTR_PID ] = { . type = NLA_U32 } ,
[ TASKSTATS_CMD_ATTR_TGID ] = { . type = NLA_U32 } ,
2006-07-14 11:24:47 +04:00
[ TASKSTATS_CMD_ATTR_REGISTER_CPUMASK ] = { . type = NLA_STRING } ,
[ TASKSTATS_CMD_ATTR_DEREGISTER_CPUMASK ] = { . type = NLA_STRING } , } ;
struct listener {
struct list_head list ;
pid_t pid ;
2006-07-14 11:24:47 +04:00
char valid ;
2006-07-14 11:24:40 +04:00
} ;
2006-07-14 11:24:47 +04:00
struct listener_list {
struct rw_semaphore sem ;
struct list_head list ;
} ;
static DEFINE_PER_CPU ( struct listener_list , listener_array ) ;
enum actions {
REGISTER ,
DEREGISTER ,
CPU_DONT_CARE
} ;
2006-07-14 11:24:40 +04:00
static int prepare_reply ( struct genl_info * info , u8 cmd , struct sk_buff * * skbp ,
2006-12-07 07:36:55 +03:00
size_t size )
2006-07-14 11:24:40 +04:00
{
struct sk_buff * skb ;
void * reply ;
/*
* If new attributes are added , please revisit this allocation
*/
2006-11-15 06:44:52 +03:00
skb = genlmsg_new ( size , GFP_KERNEL ) ;
2006-07-14 11:24:40 +04:00
if ( ! skb )
return - ENOMEM ;
if ( ! info ) {
int seq = get_cpu_var ( taskstats_seqnum ) + + ;
put_cpu_var ( taskstats_seqnum ) ;
2006-11-15 06:46:02 +03:00
reply = genlmsg_put ( skb , 0 , seq , & family , 0 , cmd ) ;
2006-07-14 11:24:40 +04:00
} else
2006-11-15 06:46:02 +03:00
reply = genlmsg_put_reply ( skb , info , & family , 0 , cmd ) ;
2006-07-14 11:24:40 +04:00
if ( reply = = NULL ) {
nlmsg_free ( skb ) ;
return - EINVAL ;
}
* skbp = skb ;
return 0 ;
}
2006-07-14 11:24:47 +04:00
/*
* Send taskstats data in @ skb to listener with nl_pid @ pid
*/
static int send_reply ( struct sk_buff * skb , pid_t pid )
2006-07-14 11:24:40 +04:00
{
struct genlmsghdr * genlhdr = nlmsg_data ( ( struct nlmsghdr * ) skb - > data ) ;
2006-07-14 11:24:47 +04:00
void * reply = genlmsg_data ( genlhdr ) ;
2006-07-14 11:24:40 +04:00
int rc ;
rc = genlmsg_end ( skb , reply ) ;
if ( rc < 0 ) {
nlmsg_free ( skb ) ;
return rc ;
}
return genlmsg_unicast ( skb , pid ) ;
}
2006-07-14 11:24:47 +04:00
/*
* Send taskstats data in @ skb to listeners registered for @ cpu ' s exit data
*/
2006-12-07 07:36:51 +03:00
static void send_cpu_listeners ( struct sk_buff * skb ,
struct listener_list * listeners )
2006-07-14 11:24:47 +04:00
{
struct genlmsghdr * genlhdr = nlmsg_data ( ( struct nlmsghdr * ) skb - > data ) ;
struct listener * s , * tmp ;
struct sk_buff * skb_next , * skb_cur = skb ;
void * reply = genlmsg_data ( genlhdr ) ;
2006-07-30 14:03:11 +04:00
int rc , delcount = 0 ;
2006-07-14 11:24:47 +04:00
rc = genlmsg_end ( skb , reply ) ;
if ( rc < 0 ) {
nlmsg_free ( skb ) ;
2006-07-30 14:03:11 +04:00
return ;
2006-07-14 11:24:47 +04:00
}
rc = 0 ;
2006-07-14 11:24:47 +04:00
down_read ( & listeners - > sem ) ;
2006-07-30 14:03:11 +04:00
list_for_each_entry ( s , & listeners - > list , list ) {
2006-07-14 11:24:47 +04:00
skb_next = NULL ;
if ( ! list_is_last ( & s - > list , & listeners - > list ) ) {
skb_next = skb_clone ( skb_cur , GFP_KERNEL ) ;
2006-07-30 14:03:11 +04:00
if ( ! skb_next )
2006-07-14 11:24:47 +04:00
break ;
}
2006-07-30 14:03:11 +04:00
rc = genlmsg_unicast ( skb_cur , s - > pid ) ;
if ( rc = = - ECONNREFUSED ) {
2006-07-14 11:24:47 +04:00
s - > valid = 0 ;
delcount + + ;
2006-07-14 11:24:47 +04:00
}
skb_cur = skb_next ;
}
2006-07-14 11:24:47 +04:00
up_read ( & listeners - > sem ) ;
2006-07-14 11:24:47 +04:00
2006-07-30 14:03:11 +04:00
if ( skb_cur )
nlmsg_free ( skb_cur ) ;
2006-07-14 11:24:47 +04:00
if ( ! delcount )
2006-07-30 14:03:11 +04:00
return ;
2006-07-14 11:24:47 +04:00
/* Delete invalidated entries */
down_write ( & listeners - > sem ) ;
list_for_each_entry_safe ( s , tmp , & listeners - > list , list ) {
if ( ! s - > valid ) {
list_del ( & s - > list ) ;
kfree ( s ) ;
}
}
up_write ( & listeners - > sem ) ;
2006-07-14 11:24:47 +04:00
}
2006-10-28 21:38:54 +04:00
static int fill_pid ( pid_t pid , struct task_struct * tsk ,
2006-07-14 11:24:40 +04:00
struct taskstats * stats )
{
2006-07-30 14:03:10 +04:00
int rc = 0 ;
2006-07-14 11:24:40 +04:00
2006-10-28 21:38:54 +04:00
if ( ! tsk ) {
rcu_read_lock ( ) ;
2006-07-14 11:24:40 +04:00
tsk = find_task_by_pid ( pid ) ;
2006-10-28 21:38:54 +04:00
if ( tsk )
get_task_struct ( tsk ) ;
rcu_read_unlock ( ) ;
if ( ! tsk )
2006-07-14 11:24:40 +04:00
return - ESRCH ;
} else
get_task_struct ( tsk ) ;
2006-12-07 07:36:54 +03:00
memset ( stats , 0 , sizeof ( * stats ) ) ;
2006-07-14 11:24:40 +04:00
/*
* Each accounting subsystem adds calls to its functions to
* fill in relevant parts of struct taskstsats as follows
*
2006-07-30 14:03:10 +04:00
* per - task - foo ( stats , tsk ) ;
2006-07-14 11:24:40 +04:00
*/
2006-07-30 14:03:10 +04:00
delayacct_add_tsk ( stats , tsk ) ;
2006-10-01 10:28:55 +04:00
/* fill in basic acct fields */
2006-07-14 11:24:41 +04:00
stats - > version = TASKSTATS_VERSION ;
2006-10-01 10:28:55 +04:00
bacct_add_tsk ( stats , tsk ) ;
2006-07-14 11:24:41 +04:00
2006-10-01 10:28:58 +04:00
/* fill in extended acct fields */
xacct_add_tsk ( stats , tsk ) ;
2006-07-14 11:24:41 +04:00
/* Define err: label here if needed */
2006-07-14 11:24:40 +04:00
put_task_struct ( tsk ) ;
return rc ;
}
2006-10-28 21:38:54 +04:00
static int fill_tgid ( pid_t tgid , struct task_struct * first ,
2006-07-14 11:24:40 +04:00
struct taskstats * stats )
{
2006-10-28 21:38:54 +04:00
struct task_struct * tsk ;
2006-07-14 11:24:44 +04:00
unsigned long flags ;
2006-10-28 21:38:54 +04:00
int rc = - ESRCH ;
2006-07-14 11:24:40 +04:00
2006-07-14 11:24:44 +04:00
/*
* Add additional stats from live tasks except zombie thread group
* leaders who are already counted with the dead tasks
*/
2006-10-28 21:38:54 +04:00
rcu_read_lock ( ) ;
if ( ! first )
2006-07-14 11:24:40 +04:00
first = find_task_by_pid ( tgid ) ;
2006-07-14 11:24:44 +04:00
2006-10-28 21:38:54 +04:00
if ( ! first | | ! lock_task_sighand ( first , & flags ) )
goto out ;
2006-07-14 11:24:44 +04:00
2006-10-28 21:38:54 +04:00
if ( first - > signal - > stats )
memcpy ( stats , first - > signal - > stats , sizeof ( * stats ) ) ;
2006-12-07 07:36:54 +03:00
else
memset ( stats , 0 , sizeof ( * stats ) ) ;
2006-10-28 21:38:49 +04:00
2006-10-28 21:38:54 +04:00
tsk = first ;
2006-07-14 11:24:40 +04:00
do {
2006-10-28 21:38:54 +04:00
if ( tsk - > exit_state )
2006-07-14 11:24:44 +04:00
continue ;
2006-07-14 11:24:40 +04:00
/*
2006-07-14 11:24:44 +04:00
* Accounting subsystem can call its functions here to
2006-07-14 11:24:40 +04:00
* fill in relevant parts of struct taskstsats as follows
*
2006-07-14 11:24:44 +04:00
* per - task - foo ( stats , tsk ) ;
2006-07-14 11:24:40 +04:00
*/
2006-07-14 11:24:44 +04:00
delayacct_add_tsk ( stats , tsk ) ;
2006-07-14 11:24:41 +04:00
2006-07-14 11:24:40 +04:00
} while_each_thread ( first , tsk ) ;
2006-07-14 11:24:41 +04:00
2006-10-28 21:38:54 +04:00
unlock_task_sighand ( first , & flags ) ;
rc = 0 ;
out :
rcu_read_unlock ( ) ;
stats - > version = TASKSTATS_VERSION ;
2006-07-14 11:24:40 +04:00
/*
2006-07-14 11:24:44 +04:00
* Accounting subsytems can also add calls here to modify
* fields of taskstats .
2006-07-14 11:24:40 +04:00
*/
2006-10-28 21:38:54 +04:00
return rc ;
2006-07-14 11:24:44 +04:00
}
static void fill_tgid_exit ( struct task_struct * tsk )
{
unsigned long flags ;
2006-10-28 21:38:53 +04:00
spin_lock_irqsave ( & tsk - > sighand - > siglock , flags ) ;
2006-07-14 11:24:44 +04:00
if ( ! tsk - > signal - > stats )
goto ret ;
/*
* Each accounting subsystem calls its functions here to
* accumalate its per - task stats for tsk , into the per - tgid structure
*
* per - task - foo ( tsk - > signal - > stats , tsk ) ;
*/
delayacct_add_tsk ( tsk - > signal - > stats , tsk ) ;
ret :
2006-10-28 21:38:53 +04:00
spin_unlock_irqrestore ( & tsk - > sighand - > siglock , flags ) ;
2006-07-14 11:24:44 +04:00
return ;
2006-07-14 11:24:40 +04:00
}
2006-07-14 11:24:47 +04:00
static int add_del_listener ( pid_t pid , cpumask_t * maskp , int isadd )
{
struct listener_list * listeners ;
struct listener * s , * tmp ;
unsigned int cpu ;
cpumask_t mask = * maskp ;
2006-07-14 11:24:44 +04:00
2006-07-14 11:24:47 +04:00
if ( ! cpus_subset ( mask , cpu_possible_map ) )
return - EINVAL ;
if ( isadd = = REGISTER ) {
for_each_cpu_mask ( cpu , mask ) {
s = kmalloc_node ( sizeof ( struct listener ) , GFP_KERNEL ,
cpu_to_node ( cpu ) ) ;
if ( ! s )
goto cleanup ;
s - > pid = pid ;
INIT_LIST_HEAD ( & s - > list ) ;
2006-07-14 11:24:47 +04:00
s - > valid = 1 ;
2006-07-14 11:24:47 +04:00
listeners = & per_cpu ( listener_array , cpu ) ;
down_write ( & listeners - > sem ) ;
list_add ( & s - > list , & listeners - > list ) ;
up_write ( & listeners - > sem ) ;
}
return 0 ;
}
/* Deregister or cleanup */
cleanup :
for_each_cpu_mask ( cpu , mask ) {
listeners = & per_cpu ( listener_array , cpu ) ;
down_write ( & listeners - > sem ) ;
list_for_each_entry_safe ( s , tmp , & listeners - > list , list ) {
if ( s - > pid = = pid ) {
list_del ( & s - > list ) ;
kfree ( s ) ;
break ;
}
}
up_write ( & listeners - > sem ) ;
}
return 0 ;
}
static int parse ( struct nlattr * na , cpumask_t * mask )
{
char * data ;
int len ;
int ret ;
if ( na = = NULL )
return 1 ;
len = nla_len ( na ) ;
if ( len > TASKSTATS_CPUMASK_MAXLEN )
return - E2BIG ;
if ( len < 1 )
return - EINVAL ;
data = kmalloc ( len , GFP_KERNEL ) ;
if ( ! data )
return - ENOMEM ;
nla_strlcpy ( data , na , len ) ;
ret = cpulist_parse ( data , * mask ) ;
kfree ( data ) ;
return ret ;
}
2006-12-07 07:36:54 +03:00
static struct taskstats * mk_reply ( struct sk_buff * skb , int type , u32 pid )
2006-12-07 07:36:53 +03:00
{
2006-12-07 07:36:54 +03:00
struct nlattr * na , * ret ;
2006-12-07 07:36:53 +03:00
int aggr ;
2006-12-07 07:36:55 +03:00
aggr = ( type = = TASKSTATS_TYPE_PID )
? TASKSTATS_TYPE_AGGR_PID
: TASKSTATS_TYPE_AGGR_TGID ;
2006-12-07 07:36:53 +03:00
na = nla_nest_start ( skb , aggr ) ;
2006-12-07 07:36:55 +03:00
if ( ! na )
goto err ;
2006-12-07 07:36:54 +03:00
if ( nla_put ( skb , type , sizeof ( pid ) , & pid ) < 0 )
goto err ;
ret = nla_reserve ( skb , TASKSTATS_TYPE_STATS , sizeof ( struct taskstats ) ) ;
if ( ! ret )
goto err ;
2006-12-07 07:36:53 +03:00
nla_nest_end ( skb , na ) ;
2006-12-07 07:36:54 +03:00
return nla_data ( ret ) ;
err :
return NULL ;
2006-12-07 07:36:53 +03:00
}
2006-07-14 11:24:47 +04:00
static int taskstats_user_cmd ( struct sk_buff * skb , struct genl_info * info )
2006-07-14 11:24:40 +04:00
{
int rc = 0 ;
struct sk_buff * rep_skb ;
2006-12-07 07:36:54 +03:00
struct taskstats * stats ;
2006-07-14 11:24:40 +04:00
size_t size ;
2006-07-14 11:24:47 +04:00
cpumask_t mask ;
rc = parse ( info - > attrs [ TASKSTATS_CMD_ATTR_REGISTER_CPUMASK ] , & mask ) ;
if ( rc < 0 )
return rc ;
if ( rc = = 0 )
return add_del_listener ( info - > snd_pid , & mask , REGISTER ) ;
rc = parse ( info - > attrs [ TASKSTATS_CMD_ATTR_DEREGISTER_CPUMASK ] , & mask ) ;
if ( rc < 0 )
return rc ;
if ( rc = = 0 )
return add_del_listener ( info - > snd_pid , & mask , DEREGISTER ) ;
2006-07-14 11:24:40 +04:00
/*
* Size includes space for nested attributes
*/
size = nla_total_size ( sizeof ( u32 ) ) +
nla_total_size ( sizeof ( struct taskstats ) ) + nla_total_size ( 0 ) ;
2006-12-07 07:36:55 +03:00
rc = prepare_reply ( info , TASKSTATS_CMD_NEW , & rep_skb , size ) ;
2006-07-14 11:24:40 +04:00
if ( rc < 0 )
return rc ;
2006-12-07 07:36:54 +03:00
rc = - EINVAL ;
2006-07-14 11:24:40 +04:00
if ( info - > attrs [ TASKSTATS_CMD_ATTR_PID ] ) {
u32 pid = nla_get_u32 ( info - > attrs [ TASKSTATS_CMD_ATTR_PID ] ) ;
2006-12-07 07:36:54 +03:00
stats = mk_reply ( rep_skb , TASKSTATS_TYPE_PID , pid ) ;
if ( ! stats )
2006-12-07 07:36:55 +03:00
goto err ;
2006-07-14 11:24:40 +04:00
2006-12-07 07:36:54 +03:00
rc = fill_pid ( pid , NULL , stats ) ;
if ( rc < 0 )
2006-12-07 07:36:55 +03:00
goto err ;
2006-07-14 11:24:40 +04:00
} else if ( info - > attrs [ TASKSTATS_CMD_ATTR_TGID ] ) {
u32 tgid = nla_get_u32 ( info - > attrs [ TASKSTATS_CMD_ATTR_TGID ] ) ;
2006-12-07 07:36:54 +03:00
stats = mk_reply ( rep_skb , TASKSTATS_TYPE_TGID , tgid ) ;
if ( ! stats )
2006-12-07 07:36:55 +03:00
goto err ;
2006-07-14 11:24:40 +04:00
2006-12-07 07:36:54 +03:00
rc = fill_tgid ( tgid , NULL , stats ) ;
if ( rc < 0 )
2006-12-07 07:36:55 +03:00
goto err ;
2006-12-07 07:36:54 +03:00
} else
2006-07-14 11:24:40 +04:00
goto err ;
2006-07-14 11:24:47 +04:00
return send_reply ( rep_skb , info - > snd_pid ) ;
2006-07-14 11:24:40 +04:00
err :
nlmsg_free ( rep_skb ) ;
return rc ;
}
2006-12-07 07:36:52 +03:00
static struct taskstats * taskstats_tgid_alloc ( struct task_struct * tsk )
{
struct signal_struct * sig = tsk - > signal ;
struct taskstats * stats ;
if ( sig - > stats | | thread_group_empty ( tsk ) )
goto ret ;
/* No problem if kmem_cache_zalloc() fails */
stats = kmem_cache_zalloc ( taskstats_cache , GFP_KERNEL ) ;
spin_lock_irq ( & tsk - > sighand - > siglock ) ;
if ( ! sig - > stats ) {
sig - > stats = stats ;
stats = NULL ;
}
spin_unlock_irq ( & tsk - > sighand - > siglock ) ;
if ( stats )
kmem_cache_free ( taskstats_cache , stats ) ;
ret :
return sig - > stats ;
}
2006-07-14 11:24:40 +04:00
/* Send pid data out on exit */
2006-12-07 07:36:51 +03:00
void taskstats_exit ( struct task_struct * tsk , int group_dead )
2006-07-14 11:24:40 +04:00
{
int rc ;
2006-12-07 07:36:51 +03:00
struct listener_list * listeners ;
2006-12-07 07:36:54 +03:00
struct taskstats * stats ;
2006-07-14 11:24:40 +04:00
struct sk_buff * rep_skb ;
size_t size ;
int is_thread_group ;
2006-10-31 09:07:15 +03:00
if ( ! family_registered )
2006-07-14 11:24:40 +04:00
return ;
/*
* Size includes space for nested attributes
*/
size = nla_total_size ( sizeof ( u32 ) ) +
nla_total_size ( sizeof ( struct taskstats ) ) + nla_total_size ( 0 ) ;
2006-12-07 07:36:52 +03:00
is_thread_group = ! ! taskstats_tgid_alloc ( tsk ) ;
2006-10-31 09:07:15 +03:00
if ( is_thread_group ) {
/* PID + STATS + TGID + STATS */
size = 2 * size ;
/* fill the tsk->signal->stats structure */
fill_tgid_exit ( tsk ) ;
}
2006-12-07 07:36:51 +03:00
listeners = & __raw_get_cpu_var ( listener_array ) ;
if ( list_empty ( & listeners - > list ) )
return ;
2006-12-07 07:36:55 +03:00
rc = prepare_reply ( NULL , TASKSTATS_CMD_NEW , & rep_skb , size ) ;
2006-07-14 11:24:40 +04:00
if ( rc < 0 )
2006-12-07 07:36:54 +03:00
return ;
2006-07-14 11:24:40 +04:00
2006-12-07 07:36:54 +03:00
stats = mk_reply ( rep_skb , TASKSTATS_TYPE_PID , tsk - > pid ) ;
if ( ! stats )
2006-12-07 07:36:55 +03:00
goto err ;
2006-07-14 11:24:40 +04:00
2006-12-07 07:36:54 +03:00
rc = fill_pid ( tsk - > pid , tsk , stats ) ;
if ( rc < 0 )
2006-12-07 07:36:55 +03:00
goto err ;
2006-07-14 11:24:40 +04:00
/*
2006-07-14 11:24:44 +04:00
* Doesn ' t matter if tsk is the leader or the last group member leaving
2006-07-14 11:24:40 +04:00
*/
2006-12-07 07:36:53 +03:00
if ( ! is_thread_group | | ! group_dead )
2006-07-14 11:24:44 +04:00
goto send ;
2006-07-14 11:24:40 +04:00
2006-12-07 07:36:54 +03:00
stats = mk_reply ( rep_skb , TASKSTATS_TYPE_TGID , tsk - > tgid ) ;
if ( ! stats )
2006-12-07 07:36:55 +03:00
goto err ;
2006-12-07 07:36:54 +03:00
memcpy ( stats , tsk - > signal - > stats , sizeof ( * stats ) ) ;
2006-07-14 11:24:40 +04:00
2006-07-14 11:24:44 +04:00
send :
2006-12-07 07:36:51 +03:00
send_cpu_listeners ( rep_skb , listeners ) ;
2006-07-14 11:24:44 +04:00
return ;
2006-12-07 07:36:55 +03:00
err :
2006-07-14 11:24:40 +04:00
nlmsg_free ( rep_skb ) ;
}
static struct genl_ops taskstats_ops = {
. cmd = TASKSTATS_CMD_GET ,
2006-07-14 11:24:47 +04:00
. doit = taskstats_user_cmd ,
2006-07-14 11:24:40 +04:00
. policy = taskstats_cmd_get_policy ,
} ;
/* Needed early in initialization */
void __init taskstats_init_early ( void )
{
2006-07-14 11:24:47 +04:00
unsigned int i ;
2006-07-14 11:24:40 +04:00
taskstats_cache = kmem_cache_create ( " taskstats_cache " ,
sizeof ( struct taskstats ) ,
0 , SLAB_PANIC , NULL , NULL ) ;
2006-07-14 11:24:47 +04:00
for_each_possible_cpu ( i ) {
INIT_LIST_HEAD ( & ( per_cpu ( listener_array , i ) . list ) ) ;
init_rwsem ( & ( per_cpu ( listener_array , i ) . sem ) ) ;
}
2006-07-14 11:24:40 +04:00
}
static int __init taskstats_init ( void )
{
int rc ;
rc = genl_register_family ( & family ) ;
if ( rc )
return rc ;
rc = genl_register_ops ( & family , & taskstats_ops ) ;
if ( rc < 0 )
goto err ;
family_registered = 1 ;
return 0 ;
err :
genl_unregister_family ( & family ) ;
return rc ;
}
/*
* late initcall ensures initialization of statistics collection
* mechanisms precedes initialization of the taskstats interface
*/
late_initcall ( taskstats_init ) ;